diff --git a/.classpath b/.classpath
deleted file mode 100644
index 701c38c..0000000
--- a/.classpath
+++ /dev/null
@@ -1,12 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/.gitignore b/.gitignore
index e660fd9..a141cd7 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1 +1,40 @@
+
+# eclipse
+.project
+.classpath
+.settings
+
+# eclipse and mcp use this
bin/
+
+# all of mcp
+CHANGELOG
+cleanup.bat
+cleanup.sh
+conf/
+decompile.bat
+decompile.sh
+docs/
+eclipse/
+jars/
+lib/
+LICENSE
+logs/
+recompile.bat
+recompile.sh
+reobf/
+reobfuscate.bat
+reobfuscate.sh
+runtime/
+startclient.bat
+startclient.sh
+startserver.bat
+startserver.sh
+temp/
+updatemcp.bat
+updatemcp.sh
+updatemd5.bat
+updatemd5.sh
+updatenames.bat
+updatenames.sh
+
diff --git a/.project b/.project
deleted file mode 100644
index c978ea6..0000000
--- a/.project
+++ /dev/null
@@ -1,17 +0,0 @@
-
-
- GuiAPI
-
-
-
-
-
- org.eclipse.jdt.core.javabuilder
-
-
-
-
-
- org.eclipse.jdt.core.javanature
-
-
diff --git a/.settings/org.eclipse.jdt.core.prefs b/.settings/org.eclipse.jdt.core.prefs
deleted file mode 100644
index f0b25bb..0000000
--- a/.settings/org.eclipse.jdt.core.prefs
+++ /dev/null
@@ -1,12 +0,0 @@
-#Mon Apr 04 18:27:12 MDT 2011
-eclipse.preferences.version=1
-org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
-org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.6
-org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve
-org.eclipse.jdt.core.compiler.compliance=1.6
-org.eclipse.jdt.core.compiler.debug.lineNumber=generate
-org.eclipse.jdt.core.compiler.debug.localVariable=generate
-org.eclipse.jdt.core.compiler.debug.sourceFile=generate
-org.eclipse.jdt.core.compiler.problem.assertIdentifier=error
-org.eclipse.jdt.core.compiler.problem.enumIdentifier=error
-org.eclipse.jdt.core.compiler.source=1.6
diff --git a/GuiAPIAnt.xml b/GuiAPIAnt.xml
new file mode 100644
index 0000000..b236a6c
--- /dev/null
+++ b/GuiAPIAnt.xml
@@ -0,0 +1,36 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/README.creole b/README.creole
deleted file mode 100644
index a37c87e..0000000
--- a/README.creole
+++ /dev/null
@@ -1,15 +0,0 @@
-= GuiAPI =
-
-GuiAPI uses the TWL library from Matthias Mann (probably spelled his name wrong), see [[http://twl.l33tlabs.org/|twl.l33tlabs.org]]
-
-==Building==
-* First decompile the needed minecraft classes with jad, then run them through astyle --break-blocks --delete-empty-lines --style=java
-* Apply the diffs in diffs/ to your decompiled minecraft and copy those java files to src/
-* Build (I use eclipse)
-** You need .minecraft/bin/*.jar, Modloader, twl/bin, and xpp on your build classpath.
-
-==Packaging==
-To create a distributable archive, package twl/bin/*, xpp*/*, theme/*, and bin/*.
-
-==Credits==
-Lots of people who I forget. Open an issue or something if you helped ...
diff --git a/bin/GuiModScreen.class b/bin/GuiModScreen.class
deleted file mode 100644
index 9f685ef..0000000
Binary files a/bin/GuiModScreen.class and /dev/null differ
diff --git a/bin/GuiModSelect.class b/bin/GuiModSelect.class
deleted file mode 100644
index 9556b2d..0000000
Binary files a/bin/GuiModSelect.class and /dev/null differ
diff --git a/bin/GuiWidgetScreen.class b/bin/GuiWidgetScreen.class
deleted file mode 100644
index 71da91f..0000000
Binary files a/bin/GuiWidgetScreen.class and /dev/null differ
diff --git a/bin/ModAction.class b/bin/ModAction.class
deleted file mode 100644
index 06263aa..0000000
Binary files a/bin/ModAction.class and /dev/null differ
diff --git a/bin/ModCallback.class b/bin/ModCallback.class
deleted file mode 100644
index 47a56c3..0000000
Binary files a/bin/ModCallback.class and /dev/null differ
diff --git a/bin/ModSettingScreen.class b/bin/ModSettingScreen.class
deleted file mode 100644
index f1e18ea..0000000
Binary files a/bin/ModSettingScreen.class and /dev/null differ
diff --git a/bin/ModSettings.class b/bin/ModSettings.class
deleted file mode 100644
index 927240d..0000000
Binary files a/bin/ModSettings.class and /dev/null differ
diff --git a/bin/ScreenScaleProxy.class b/bin/ScreenScaleProxy.class
deleted file mode 100644
index a89b4cc..0000000
Binary files a/bin/ScreenScaleProxy.class and /dev/null differ
diff --git a/bin/Setting.class b/bin/Setting.class
deleted file mode 100644
index 574d7b0..0000000
Binary files a/bin/Setting.class and /dev/null differ
diff --git a/bin/SettingBoolean.class b/bin/SettingBoolean.class
deleted file mode 100644
index 03aa6de..0000000
Binary files a/bin/SettingBoolean.class and /dev/null differ
diff --git a/bin/SettingFloat.class b/bin/SettingFloat.class
deleted file mode 100644
index 47154f7..0000000
Binary files a/bin/SettingFloat.class and /dev/null differ
diff --git a/bin/SettingInt.class b/bin/SettingInt.class
deleted file mode 100644
index b7cecd8..0000000
Binary files a/bin/SettingInt.class and /dev/null differ
diff --git a/bin/SettingKey.class b/bin/SettingKey.class
deleted file mode 100644
index 1981d13..0000000
Binary files a/bin/SettingKey.class and /dev/null differ
diff --git a/bin/SettingMulti.class b/bin/SettingMulti.class
deleted file mode 100644
index 9179c7e..0000000
Binary files a/bin/SettingMulti.class and /dev/null differ
diff --git a/bin/SettingText.class b/bin/SettingText.class
deleted file mode 100644
index ce2c3a6..0000000
Binary files a/bin/SettingText.class and /dev/null differ
diff --git a/bin/Subscreen.class b/bin/Subscreen.class
deleted file mode 100644
index 895e21b..0000000
Binary files a/bin/Subscreen.class and /dev/null differ
diff --git a/bin/WidgetBoolean.class b/bin/WidgetBoolean.class
deleted file mode 100644
index 7505ee7..0000000
Binary files a/bin/WidgetBoolean.class and /dev/null differ
diff --git a/bin/WidgetClassicTwocolumn.class b/bin/WidgetClassicTwocolumn.class
deleted file mode 100644
index d7cc426..0000000
Binary files a/bin/WidgetClassicTwocolumn.class and /dev/null differ
diff --git a/bin/WidgetClassicWindow.class b/bin/WidgetClassicWindow.class
deleted file mode 100644
index 0d9c8b9..0000000
Binary files a/bin/WidgetClassicWindow.class and /dev/null differ
diff --git a/bin/WidgetFloat.class b/bin/WidgetFloat.class
deleted file mode 100644
index 79832a1..0000000
Binary files a/bin/WidgetFloat.class and /dev/null differ
diff --git a/bin/WidgetInt.class b/bin/WidgetInt.class
deleted file mode 100644
index 920b85f..0000000
Binary files a/bin/WidgetInt.class and /dev/null differ
diff --git a/bin/WidgetKeybinding.class b/bin/WidgetKeybinding.class
deleted file mode 100644
index 1584afc..0000000
Binary files a/bin/WidgetKeybinding.class and /dev/null differ
diff --git a/bin/WidgetMulti.class b/bin/WidgetMulti.class
deleted file mode 100644
index e473399..0000000
Binary files a/bin/WidgetMulti.class and /dev/null differ
diff --git a/bin/WidgetSetting.class b/bin/WidgetSetting.class
deleted file mode 100644
index 3d758bf..0000000
Binary files a/bin/WidgetSetting.class and /dev/null differ
diff --git a/bin/WidgetSimplewindow.class b/bin/WidgetSimplewindow.class
deleted file mode 100644
index 096b5b1..0000000
Binary files a/bin/WidgetSimplewindow.class and /dev/null differ
diff --git a/bin/WidgetSingleRow.class b/bin/WidgetSingleRow.class
deleted file mode 100644
index 4c5f949..0000000
Binary files a/bin/WidgetSingleRow.class and /dev/null differ
diff --git a/bin/WidgetSinglecolumn.class b/bin/WidgetSinglecolumn.class
deleted file mode 100644
index d97d871..0000000
Binary files a/bin/WidgetSinglecolumn.class and /dev/null differ
diff --git a/bin/WidgetSlider.class b/bin/WidgetSlider.class
deleted file mode 100644
index e4eab73..0000000
Binary files a/bin/WidgetSlider.class and /dev/null differ
diff --git a/bin/WidgetText.class b/bin/WidgetText.class
deleted file mode 100644
index 28431f9..0000000
Binary files a/bin/WidgetText.class and /dev/null differ
diff --git a/bin/ch.class b/bin/ch.class
deleted file mode 100644
index 425b726..0000000
Binary files a/bin/ch.class and /dev/null differ
diff --git a/bin/oa.class b/bin/oa.class
deleted file mode 100644
index 0d04200..0000000
Binary files a/bin/oa.class and /dev/null differ
diff --git a/bin/qp.class b/bin/qp.class
deleted file mode 100644
index b1a829f..0000000
Binary files a/bin/qp.class and /dev/null differ
diff --git a/bin/uq.class b/bin/uq.class
deleted file mode 100644
index dd409e3..0000000
Binary files a/bin/uq.class and /dev/null differ
diff --git a/mcp/sharose/mods/guiapi/GuiAPI.java b/mcp/sharose/mods/guiapi/GuiAPI.java
new file mode 100644
index 0000000..91e9a6a
--- /dev/null
+++ b/mcp/sharose/mods/guiapi/GuiAPI.java
@@ -0,0 +1,160 @@
+package sharose.mods.guiapi;
+
+import java.lang.reflect.Field;
+import java.util.ArrayList;
+import java.util.EnumSet;
+import java.util.List;
+import java.util.Map;
+
+import cpw.mods.fml.common.ITickHandler;
+import cpw.mods.fml.common.Mod;
+import cpw.mods.fml.common.ObfuscationReflectionHelper;
+import cpw.mods.fml.common.Mod.Init;
+import cpw.mods.fml.common.Mod.PreInit;
+import cpw.mods.fml.common.TickType;
+import cpw.mods.fml.common.event.FMLInitializationEvent;
+import cpw.mods.fml.common.event.FMLPreInitializationEvent;
+import cpw.mods.fml.common.registry.TickRegistry;
+import cpw.mods.fml.relauncher.IFMLLoadingPlugin;
+import cpw.mods.fml.relauncher.Side;
+
+import net.minecraft.client.Minecraft;
+import net.minecraft.client.gui.*;
+import net.minecraft.client.settings.EnumOptions;
+import net.minecraft.src.BaseMod;
+import net.minecraft.src.ModLoader;
+
+@Mod(name = "GuiAPI", modid = "GuiAPI", version = "0.15.5 - DEBUG", acceptedMinecraftVersions = "1.5.1")
+public class GuiAPI implements ITickHandler {
+
+ Object cacheCheck = null;
+ Field controlListField;
+
+ @Init
+ public void init(FMLInitializationEvent event) {
+
+ try
+ {
+ controlListField = GuiScreen.class.getDeclaredField(ObfuscationReflectionHelper
+ .remapFieldNames("net.minecraft.client.gui.GuiScreen", "buttonList")[0]);
+ controlListField.setAccessible(true);
+ }
+ catch(Throwable e)
+ {
+ try
+ {
+ controlListField = GuiScreen.class.getDeclaredField("buttonList");
+ controlListField.setAccessible(true);
+ }
+ catch(Throwable e2)
+ {
+ try {
+ Field[] fields = GuiScreen.class.getDeclaredFields();
+ for (int i = 0; i < fields.length; i++) {
+ if (fields[i].getType() == List.class) {
+ controlListField = fields[i];
+ controlListField.setAccessible(true);
+ break;
+ }
+ }
+ if (controlListField == null) {
+ throw new Exception("No fields found on GuiScreen ("
+ + GuiScreen.class.getSimpleName()
+ + ") of type List! This should never happen!");
+ }
+ } catch (Throwable e3) {
+ throw new RuntimeException(
+ "Unable to get Field reference for GuiScreen.controlList!",
+ e3);
+ }
+ }
+
+ }
+
+
+ TickRegistry.registerTickHandler(this, Side.CLIENT);
+ }
+
+ public List getControlList(GuiOptions gui) {
+ try {
+ return (List) controlListField.get(gui);
+ } catch (Throwable e) {
+ return null; // This should really print something, but it should
+ // never (ever) fire.
+ }
+ }
+
+ public void processGuiOptions(GuiOptions gui) {
+ List controlList = getControlList(gui);
+ if (controlList == null) {
+ return;
+ }
+ if (controlList.get(0) == cacheCheck) {
+ // Cached so we don't have to check this every frame
+ return;
+ }
+
+ // I hacked this out so it just sticks it between touchscreen mode and difficulty. I'm so sorry.
+
+ // First get a list of buttons
+ ArrayList buttonsPreSorted = new ArrayList();
+ for (Object guiButton : controlList) {
+ if (guiButton instanceof GuiSmallButton)
+ buttonsPreSorted.add((GuiSmallButton) guiButton);
+ }
+
+
+
+ int xPos = -1; // difficulty
+ int yPos = -1; // touchscreen mode
+ for (GuiSmallButton guiButton : buttonsPreSorted) {
+ if(guiButton.returnEnumOptions() == EnumOptions.DIFFICULTY)
+ {
+ xPos = guiButton.xPosition;
+ }
+
+ if(guiButton.returnEnumOptions() == EnumOptions.TOUCHSCREEN)
+ {
+ yPos = guiButton.yPosition;
+ }
+ }
+
+
+ controlList.add(new GuiApiButton(300, xPos, yPos, 150, 20,
+ "Global Mod Options"));
+
+ // set the cache!
+ cacheCheck = controlList.get(0);
+ }
+
+ @Override
+ public void tickStart(EnumSet type, Object... tickData) {
+ if (!type.contains(TickType.RENDER)) {
+ return;
+ }
+ if (Minecraft.getMinecraft() == null) {
+ return; // what
+ }
+ if (Minecraft.getMinecraft().currentScreen == null) {
+ return;
+ }
+ if (Minecraft.getMinecraft().currentScreen instanceof GuiOptions) {
+ processGuiOptions((GuiOptions) Minecraft.getMinecraft().currentScreen);
+ }
+ }
+
+ @Override
+ public void tickEnd(EnumSet type, Object... tickData) {
+
+ }
+
+ @Override
+ public EnumSet ticks() {
+ return EnumSet.of(TickType.RENDER);
+ }
+
+ @Override
+ public String getLabel() {
+ return "GuiAPI main menu checker";
+ }
+}
diff --git a/mcp/sharose/mods/guiapi/GuiAPIDummyCoreMod.java b/mcp/sharose/mods/guiapi/GuiAPIDummyCoreMod.java
new file mode 100644
index 0000000..eb8bf94
--- /dev/null
+++ b/mcp/sharose/mods/guiapi/GuiAPIDummyCoreMod.java
@@ -0,0 +1,32 @@
+package sharose.mods.guiapi;
+
+import java.util.Map;
+
+import cpw.mods.fml.relauncher.IFMLLoadingPlugin;
+
+@IFMLLoadingPlugin.MCVersion("1.5.1")
+public class GuiAPIDummyCoreMod implements IFMLLoadingPlugin {
+ @Override
+ public String[] getLibraryRequestClass() {
+ return null;
+ }
+
+ @Override
+ public String[] getASMTransformerClass() {
+ return null;
+ }
+
+ @Override
+ public String getModContainerClass() {
+ return null;
+ }
+
+ @Override
+ public String getSetupClass() {
+ return null;
+ }
+
+ @Override
+ public void injectData(Map data) {
+ }
+}
diff --git a/mcp/sharose/mods/guiapi/GuiApiButton.java b/mcp/sharose/mods/guiapi/GuiApiButton.java
new file mode 100644
index 0000000..38d61e7
--- /dev/null
+++ b/mcp/sharose/mods/guiapi/GuiApiButton.java
@@ -0,0 +1,26 @@
+package sharose.mods.guiapi;
+
+import net.minecraft.client.Minecraft;
+import net.minecraft.client.gui.GuiButton;
+
+public class GuiApiButton extends GuiButton {
+
+ public GuiApiButton(int par1, int par2, int par3, int par4, int par5,
+ String par6Str) {
+ super(par1, par2, par3, par4, par5, par6Str);
+ }
+
+ @Override
+ public boolean mousePressed(Minecraft par1Minecraft, int par2, int par3)
+ {
+ if(super.mousePressed(par1Minecraft, par2, par3))
+ {
+ par1Minecraft.gameSettings.saveOptions();
+ ModSettingScreen.guiContext = "";
+ WidgetSetting.updateAll();
+ GuiModScreen.show(new GuiModSelect(par1Minecraft.currentScreen));
+ return true;
+ }
+ return false;
+ }
+}
diff --git a/mcp/sharose/mods/guiapi/GuiApiFontHelper.java b/mcp/sharose/mods/guiapi/GuiApiFontHelper.java
new file mode 100644
index 0000000..7b7e4a1
--- /dev/null
+++ b/mcp/sharose/mods/guiapi/GuiApiFontHelper.java
@@ -0,0 +1,257 @@
+package sharose.mods.guiapi;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import de.matthiasmann.twl.Color;
+import de.matthiasmann.twl.EditField;
+import de.matthiasmann.twl.TextWidget;
+import de.matthiasmann.twl.Widget;
+import de.matthiasmann.twl.renderer.AnimationStateString;
+import de.matthiasmann.twl.renderer.lwjgl.LWJGLFont;
+
+/**
+ * This class is designed to enable you to make clones of the GuiAPI font,
+ * colour it and add options as you want, and then set that font to specific
+ * kinds of widgets.
+ *
+ * @author ShaRose
+ *
+ */
+public class GuiApiFontHelper {
+ /**
+ * These are the font states you can use for your settings. Most of the
+ * time, the only ones you will use would be normal and hover.
+ *
+ * @author ShaRose
+ *
+ */
+ public enum FontStates {
+ disabled, error, hover, normal, textSelection, warning
+ }
+
+ private static Map customFontWidgets;
+ private static Map stateTable;
+ static {
+ GuiApiFontHelper.customFontWidgets = new HashMap();
+ try {
+ GuiApiFontHelper.stateTable = new HashMap();
+ FontStates[] states = FontStates.values();
+ for (int i = 0; i < states.length; i++) {
+ GuiApiFontHelper.stateTable.put(states[i],
+ new AnimationStateString(states[i].name()));
+ }
+ } catch (Throwable e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * This method is used internally to resync the font references. This has to
+ * be used each time the theme is applied (After you set the screen,
+ * specifically), otherwise TWL will automatically replace it with the
+ * default font.
+ */
+ public static void resyncCustomFonts() {
+ for (Map.Entry entry : GuiApiFontHelper.customFontWidgets
+ .entrySet()) {
+ // probably going to want to optimize this I think
+ GuiApiFontHelper font = entry.getValue();
+ Widget widget = entry.getKey();
+ if (widget instanceof TextWidget) {
+ font.setFont((TextWidget) widget);
+ }
+ if (widget instanceof EditField) {
+ font.setFont((EditField) widget);
+ }
+ if (widget instanceof WidgetText) {
+ font.setFont((WidgetText) widget);
+ }
+ }
+ }
+
+ private LWJGLFont myFont;
+
+ /**
+ * This creates a new GuiApiFontHelper with it's own internal font
+ * reference.
+ */
+ public GuiApiFontHelper() {
+ GuiWidgetScreen widgetScreen = GuiWidgetScreen.getInstance();
+ LWJGLFont baseFont = (LWJGLFont) widgetScreen.theme.getDefaultFont();
+ myFont = baseFont.clone();
+ }
+
+ /**
+ * @param state
+ * The font state you want to check.
+ * @return The Color for this font according to the specified state.
+ */
+ public Color getColor(FontStates state) {
+ return myFont.evalFontState(GuiApiFontHelper.stateTable.get(state))
+ .getColor();
+ }
+
+ /**
+ * @param state
+ * The font state you want to check.
+ * @return The LineThrough for this font according to the specified state.
+ */
+ public boolean getLineThrough(FontStates state) {
+ return myFont.evalFontState(GuiApiFontHelper.stateTable.get(state))
+ .getLineThrough();
+ }
+
+ /**
+ * @param state
+ * The font state you want to check.
+ * @return The OffsetX for this font according to the specified state.
+ */
+ public int getOffsetX(FontStates state) {
+ return myFont.evalFontState(GuiApiFontHelper.stateTable.get(state))
+ .getOffsetX();
+ }
+
+ /**
+ * @param state
+ * The font state you want to check.
+ * @return The OffsetY for this font according to the specified state.
+ */
+ public int getOffsetY(FontStates state) {
+ return myFont.evalFontState(GuiApiFontHelper.stateTable.get(state))
+ .getOffsetY();
+ }
+
+ /**
+ * @param state
+ * The font state you want to check.
+ * @return The Underline for this font according to the specified state.
+ */
+ public boolean getUnderline(FontStates state) {
+ return myFont.evalFontState(GuiApiFontHelper.stateTable.get(state))
+ .getUnderline();
+ }
+
+ /**
+ * @param state
+ * The font state you want to check.
+ * @return The UnderlineOffset for this font according to the specified
+ * state.
+ */
+ public int getUnderlineOffset(FontStates state) {
+ return myFont.evalFontState(GuiApiFontHelper.stateTable.get(state))
+ .getUnderlineOffset();
+ }
+
+ /**
+ * @param state
+ * The font state you want to set.
+ * @param col
+ * The Color you wish to this fontstate to have for this font.
+ */
+ public void setColor(FontStates state, Color col) {
+ myFont.evalFontState(GuiApiFontHelper.stateTable.get(state)).setColor(
+ col);
+ GuiApiFontHelper.resyncCustomFonts();
+ }
+
+ /**
+ * @param widget
+ * The EditField (Or derived class) you wish to set.
+ */
+ public void setFont(EditField widget) {
+ try {
+ setFont(widget.textRenderer);
+ GuiApiFontHelper.customFontWidgets.put(widget, this);
+ } catch (Throwable e) {
+ e.printStackTrace();
+ }
+ }
+
+ /**
+ * @param widget
+ * The TextWidget (Or derived class) you wish to set.
+ */
+ public void setFont(TextWidget widget) {
+ widget.setFont(myFont);
+ GuiApiFontHelper.customFontWidgets.put(widget, this);
+ }
+
+ /**
+ * @param widget
+ * The WidgetText (Or derived class) you wish to set. This will
+ * set the display label (if it has one) and the edit field.
+ */
+ public void setFont(WidgetText widget) {
+ if (widget.displayLabel != null) {
+ widget.displayLabel.setFont(myFont);
+ GuiApiFontHelper.customFontWidgets.put(widget, this);
+ }
+ setFont(widget.editField);
+ GuiApiFontHelper.customFontWidgets.put(widget, this);
+ }
+
+ /**
+ * @param state
+ * The font state you want to set.
+ * @param val
+ * The LineThrough you wish to this fontstate to have for this
+ * font.
+ */
+ public void setLineThrough(FontStates state, boolean val) {
+ myFont.evalFontState(GuiApiFontHelper.stateTable.get(state))
+ .setLineThrough(val);
+ GuiApiFontHelper.resyncCustomFonts();
+ }
+
+ /**
+ * @param state
+ * The font state you want to set.
+ * @param i
+ * The OffsetX you wish to this fontstate to have for this font.
+ */
+ public void setOffsetX(FontStates state, int i) {
+ myFont.evalFontState(GuiApiFontHelper.stateTable.get(state))
+ .setOffsetX(i);
+ GuiApiFontHelper.resyncCustomFonts();
+ }
+
+ /**
+ * @param state
+ * The font state you want to set.
+ * @param i
+ * The OffsetY you wish to this fontstate to have for this font.
+ */
+ public void setOffsetY(FontStates state, int i) {
+ myFont.evalFontState(GuiApiFontHelper.stateTable.get(state))
+ .setOffsetY(i);
+ GuiApiFontHelper.resyncCustomFonts();
+ }
+
+ /**
+ * @param state
+ * The font state you want to set.
+ * @param val
+ * The Underline you wish to this fontstate to have for this
+ * font.
+ */
+ public void setUnderline(FontStates state, boolean val) {
+ myFont.evalFontState(GuiApiFontHelper.stateTable.get(state))
+ .setUnderline(val);
+ GuiApiFontHelper.resyncCustomFonts();
+ }
+
+ /**
+ * @param state
+ * The font state you want to set.
+ * @param i
+ * The UnderlineOffset you wish to this fontstate to have for
+ * this font.
+ */
+ public void setUnderlineOffset(FontStates state, int i) {
+ myFont.evalFontState(GuiApiFontHelper.stateTable.get(state))
+ .setUnderlineOffset(i);
+ GuiApiFontHelper.resyncCustomFonts();
+ }
+
+}
diff --git a/mcp/sharose/mods/guiapi/GuiApiHelper.java b/mcp/sharose/mods/guiapi/GuiApiHelper.java
new file mode 100644
index 0000000..6fef51a
--- /dev/null
+++ b/mcp/sharose/mods/guiapi/GuiApiHelper.java
@@ -0,0 +1,368 @@
+package sharose.mods.guiapi;
+
+import java.util.AbstractMap;
+import java.util.ArrayList;
+
+import de.matthiasmann.twl.Button;
+import de.matthiasmann.twl.TextArea;
+import de.matthiasmann.twl.Widget;
+import de.matthiasmann.twl.model.SimpleButtonModel;
+import de.matthiasmann.twl.textarea.HTMLTextAreaModel;
+import de.matthiasmann.twl.textarea.SimpleTextAreaModel;
+import de.matthiasmann.twl.textarea.TextAreaModel;
+
+/**
+ * This is just a class for helping ease common and somewhat long operations
+ * with GuiAPI.
+ *
+ * @author ShaRose
+ */
+public class GuiApiHelper {
+ /**
+ * This is a static ModAction to go back to the previous menu.
+ */
+ public final static ModAction backModAction;
+ /**
+ * This is a static ModAction to play the 'click' sound you usually hear
+ * when pressing a button in minecraft.
+ */
+ public final static ModAction clickModAction;
+
+ static {
+ backModAction = new ModAction(GuiModScreen.class, "back");
+ GuiApiHelper.backModAction.setTag("Helper Back ModAction");
+ clickModAction = new ModAction(GuiModScreen.class, "clicksound");
+ GuiApiHelper.clickModAction.setTag("Helper ClickSound ModAction");
+ }
+
+ /**
+ * This method is one of the overloads to create a choice menu, so the user
+ * is presented a textbox and user configurable buttons beneath it. This
+ * overload is the most advanced option, though uses more code. Call this
+ * method, use the returned GuiApiHelper instance to add the buttons you
+ * want, then generate the widget.
+ *
+ * @param displayText
+ * The text to be displayed to the user.
+ * @return An instance of GuiApiHelper. Use the addButton methods to add
+ * buttons to the menu, and then when you are done use genWidget to
+ * create the choice menu.
+ */
+ public static GuiApiHelper createChoiceMenu(String displayText) {
+ return new GuiApiHelper(displayText);
+ }
+
+ /**
+ * This method is one of the overloads to create a choice menu, so the user
+ * is presented a textbox and user configurable buttons beneath it. This
+ * overload uses variable arguments to choose it.
+ *
+ * @param displayText
+ * The text to display.
+ * @param showBackButton
+ * Whether to automatically show a 'back' button or not.
+ * @param autoBack
+ * Whether to automatically merge a 'back' ModAction with the
+ * buttons.
+ * @param args
+ * The button information. Enter it in the form of String (Name
+ * on the button), ModAction (The ModAction to call when the
+ * button is pressed).
+ * @return The generated widget. Use GuiModScreen.show to display it.
+ */
+ public static Widget createChoiceMenu(String displayText,
+ Boolean showBackButton, Boolean autoBack, Object... args) {
+ if ((args.length % 2) == 1) {
+ throw new IllegalArgumentException(
+ "Arguments not in correct format. You need to have an even number of arguments, in the form of String, ModAction for each button.");
+ }
+ GuiApiHelper helper = new GuiApiHelper(displayText);
+ try {
+ for (int i = 0; i < args.length; i += 2) {
+ helper.addButton((String) args[i], (ModAction) args[i + 1],
+ autoBack);
+ }
+ } catch (Throwable e) {
+ throw new IllegalArgumentException(
+ "Arguments not in correct format. You need to have an even number of arguments, in the form of String, ModAction for each button.",
+ e);
+ }
+ return helper.genWidget(showBackButton);
+ }
+
+ /**
+ * This method is one of the overloads to create a choice menu, so the user
+ * is presented a textbox and user configurable buttons beneath it. This
+ * overload uses two tables that match up to create the buttons.
+ *
+ * @param displayText
+ * The text to display.
+ * @param showBackButton
+ * Whether to automatically show a 'back' button or not.
+ * @param autoBack
+ * Whether to automatically merge a 'back' ModAction with the
+ * buttons.
+ * @param buttonTexts
+ * The text for the buttons you want to show.
+ * @param buttonActions
+ * The corresponding ModActions for the buttons.
+ * @return The generated widget. Use GuiModScreen.show to display it.
+ */
+ public static Widget createChoiceMenu(String displayText,
+ Boolean showBackButton, Boolean autoBack, String[] buttonTexts,
+ ModAction[] buttonActions) {
+ if (buttonTexts.length != buttonActions.length) {
+ throw new IllegalArgumentException(
+ "Arguments not in correct format. buttonTexts needs to be the same size as buttonActions.");
+ }
+ GuiApiHelper helper = new GuiApiHelper(displayText);
+ for (int i = 0; i < buttonTexts.length; i += 2) {
+ helper.addButton(buttonTexts[i], buttonActions[i], autoBack);
+ }
+ return helper.genWidget(showBackButton);
+ }
+
+ /**
+ * This method creates a button widget for you.
+ *
+ * @param displayText
+ * The text to display on the button.
+ * @param action
+ * The ModAction to call when clicked.
+ * @param addClick
+ * Set this to true and it will automatically play the Click
+ * sound.
+ * @return The new Button widget.
+ */
+ public static Button makeButton(String displayText, ModAction action,
+ Boolean addClick) {
+ SimpleButtonModel simplebuttonmodel = new SimpleButtonModel();
+ if (addClick) {
+ action = action.mergeAction(GuiApiHelper.clickModAction);
+ }
+ simplebuttonmodel.addActionCallback(action);
+ Button button = new Button(simplebuttonmodel);
+ button.setText(displayText);
+ return button;
+ }
+
+ /**
+ * This method creates a button widget for you.
+ *
+ * @param displayText
+ * The text to display on the button.
+ * @param methodName
+ * The name of the method to call when clicked.
+ * @param me
+ * The Object or Class that has the method you want to call.
+ * @param addClick
+ * Set this to true and it will automatically play the Click
+ * sound.
+ * @return The new Button widget.
+ */
+ public static Button makeButton(String displayText, String methodName,
+ Object me, Boolean addClick) {
+ return GuiApiHelper.makeButton(displayText, new ModAction(me,
+ methodName), addClick);
+ }
+
+ /**
+ * This method creates a button widget for you.
+ *
+ * @param displayText
+ * The text to display on the button.
+ * @param methodName
+ * The name of the method to call when clicked.
+ * @param me
+ * The Object or Class that has the method you want to call.
+ * @param addClick
+ * Set this to true and it will automatically play the Click
+ * sound.
+ * @param classes
+ * The argument classes for the method you want to call.
+ * @param arguments
+ * The defaulted arguments you want to use.
+ * @return The new Button widget.
+ */
+ @SuppressWarnings("rawtypes")
+ public static Button makeButton(String displayText, String methodName,
+ Object me, Boolean addClick, Class[] classes, Object... arguments) {
+ return GuiApiHelper.makeButton(displayText, new ModAction(me,
+ methodName, classes).setDefaultArguments(arguments), addClick);
+ }
+
+ /**
+ * This is a small helper to create TextAreas, which is basically a label
+ * that wraps text.
+ *
+ * @param text
+ * The text to show.
+ * @param htmlMode
+ * Whether to create the Textbox to render HTML, or standard
+ * text.
+ * @return The TextArea widget.
+ */
+ public static TextArea makeTextArea(String text, Boolean htmlMode) {
+ if (!htmlMode) {
+ SimpleTextAreaModel model = new SimpleTextAreaModel();
+ model.setText(text, false);
+ return new TextArea(model);
+ }
+ HTMLTextAreaModel model = new HTMLTextAreaModel();
+ model.setHtml(text);
+ return new TextArea(model);
+ }
+
+ /**
+ * This method is designed to provide an easy way to make popups or
+ * information notices.
+ *
+ * @param titleText
+ * This is the text for the title on top of the display. If you
+ * set this to null, it will simply not have a top bar.
+ * @param displayText
+ * This is the text to be displayed below the title bar (If there
+ * is one).
+ * @param buttonText
+ * This is the text you want the back button to have. Something
+ * like 'OK' or 'Back' is what you usually use.
+ * @param htmlMode
+ * This is if you want the text to be rendered as if it were
+ * HTML.
+ * @return The generated widget. Use GuiModScreen.show to display it.
+ */
+ public static Widget makeTextDisplayAndGoBack(String titleText,
+ String displayText, String buttonText, Boolean htmlMode) {
+ WidgetSinglecolumn widget = new WidgetSinglecolumn(new Widget[0]);
+ widget.add(GuiApiHelper.makeTextArea(displayText, htmlMode));
+ widget.overrideHeight = false;
+ WidgetSimplewindow window = new WidgetSimplewindow(widget, titleText);
+ window.backButton.setText(buttonText);
+ return window;
+ }
+
+ /**
+ * This is a small helper method to set the text of a TextArea. It supposed
+ * Simple and HTML TextAreas.
+ *
+ * @param textArea
+ * The TextArea you wish to set the text of.
+ * @param text
+ * The text to set.
+ */
+ public static void setTextAreaText(TextArea textArea, String text) {
+ TextAreaModel model = textArea.getModel();
+ if (model instanceof SimpleTextAreaModel) {
+ ((SimpleTextAreaModel) model).setText(text, false);
+ } else {
+ ((HTMLTextAreaModel) model).setHtml(text);
+ }
+ }
+
+ /** The button info. */
+ private ArrayList> buttonInfo_;
+
+ /** The display text. */
+ private String displayText_;
+
+ /**
+ * Instantiates a new gui api helper.
+ *
+ * @param displayText
+ * the display text
+ */
+ private GuiApiHelper(String displayText) {
+ displayText_ = displayText;
+ buttonInfo_ = new ArrayList>();
+ }
+
+ /**
+ * This method adds a button to the choice menu. The arguments are the same
+ * as the related makeButton method.
+ *
+ * @param text
+ * The text for the button.
+ * @param action
+ * The action to use when pressed.
+ * @param mergeBack
+ * Whether or not to automatically to back after the button is
+ * pressed.
+ */
+ public void addButton(String text, ModAction action, Boolean mergeBack) {
+ ModAction buttonAction = action;
+ if (mergeBack) {
+ buttonAction = buttonAction.mergeAction(GuiApiHelper.backModAction);
+ buttonAction.setTag("Button '" + text + "' with back.");
+ }
+ buttonInfo_.add(new AbstractMap.SimpleEntry(text,
+ buttonAction));
+ }
+
+ /**
+ * This method adds a button to the choice menu. The arguments are the same
+ * as the related makeButton method.
+ *
+ * @param text
+ * The text for the button.
+ * @param methodName
+ * The method to call.
+ * @param me
+ * The object or class with the method you wish to call.
+ * @param mergeBack
+ * Whether or not to automatically to back after the button is
+ * pressed.
+ */
+ public void addButton(String text, String methodName, Object me,
+ Boolean mergeBack) {
+ addButton(text, new ModAction(me, methodName), mergeBack);
+ }
+
+ /**
+ * This method adds a button to the choice menu. The arguments are the same
+ * as the related makeButton method.
+ *
+ * @param text
+ * The text for the button.
+ * @param methodName
+ * The method to call.
+ * @param me
+ * The object or class with the method you wish to call.
+ * @param types
+ * The types of the arguments required for the method.
+ * @param mergeBack
+ * Whether or not to automatically to back after the button is
+ * pressed.
+ * @param arguments
+ * The arguments you wish to use when this button is pressed.
+ */
+ @SuppressWarnings("rawtypes")
+ public void addButton(String text, String methodName, Object me,
+ Class[] types, Boolean mergeBack, Object... arguments) {
+ addButton(text,
+ new ModAction(me, methodName, types)
+ .setDefaultArguments(arguments), mergeBack);
+ }
+
+ /**
+ * This creates the Choice Menu from the Display Text entered earlier and
+ * the buttons you have added.
+ *
+ * @param showBackButton
+ * If true, show a bar on the bottom with a button to go back to
+ * the previous menu. If false, don't.
+ * @return The generated widget. Use GuiModScreen.show to display it.
+ */
+ public WidgetSimplewindow genWidget(Boolean showBackButton) {
+ WidgetSinglecolumn widget = new WidgetSinglecolumn(new Widget[0]);
+ TextArea textarea = GuiApiHelper.makeTextArea(displayText_, false);
+ widget.add(textarea);
+ widget.heightOverrideExceptions.put(textarea, 0);
+ for (AbstractMap.SimpleEntry entry : buttonInfo_) {
+ widget.add(GuiApiHelper.makeButton(entry.getKey(),
+ entry.getValue(), true));
+ }
+ WidgetSimplewindow window = new WidgetSimplewindow(widget, null,
+ showBackButton);
+ return window;
+ }
+}
diff --git a/mcp/sharose/mods/guiapi/GuiModScreen.java b/mcp/sharose/mods/guiapi/GuiModScreen.java
new file mode 100644
index 0000000..273e426
--- /dev/null
+++ b/mcp/sharose/mods/guiapi/GuiModScreen.java
@@ -0,0 +1,161 @@
+package sharose.mods.guiapi;
+
+import net.minecraft.client.Minecraft;
+import net.minecraft.client.gui.GuiScreen;
+import net.minecraft.client.gui.ScaledResolution;
+import de.matthiasmann.twl.Widget;
+import de.matthiasmann.twl.renderer.lwjgl.LWJGLRenderer;
+import de.matthiasmann.twl.renderer.lwjgl.RenderScale;
+
+/**
+ * GuiModScreen is the minecraft screen subclass that controls and renders TWL.
+ * normally you will want to call it's static methods to use it, though
+ * subclassing it and/or instantiating it are also possible. however, to do so
+ * would use unsafe api (I still might change things.)
+ *
+ * @author lahwran
+ *
+ * @see GuiModScreen#show(GuiModScreen)
+ * @see GuiModScreen#show(Widget)
+ */
+public class GuiModScreen extends GuiScreen {
+ /**
+ * Used by static methods. Is the currently displayed screen.
+ */
+ public static GuiModScreen currentScreen;
+
+ /**
+ * Hide current screen and show parent screen.
+ */
+ public static void back() {
+ if (GuiModScreen.currentScreen != null) {
+ Minecraft m = ModSettings.getMcinst();
+ m.displayGuiScreen(GuiModScreen.currentScreen.parentScreen);
+ if (GuiModScreen.currentScreen.parentScreen instanceof GuiModScreen) {
+ GuiModScreen.currentScreen = (GuiModScreen) GuiModScreen.currentScreen.parentScreen;
+ GuiModScreen.currentScreen.setActive();
+ } else {
+ GuiModScreen.currentScreen = null;
+ }
+ }
+ }
+
+ /**
+ * Play a click sound. Call after the user performs an action. Already
+ * called from setting widgets.
+ */
+ public static void clicksound() {
+ Minecraft m = ModSettings.getMcinst();
+ m.sndManager.playSoundFX("random.click", 1.0F, 1.0F);
+ }
+
+ /**
+ * Show a screen - GuiModScreen version. Show an instance of GuiModScreen -
+ * Does not set the parent screen, you have to deal with that yourself!
+ *
+ * @param screen
+ * GuiModScreen instance or subclass to show with parent screen
+ * already set to current screen.
+ */
+ public static void show(GuiModScreen screen) {
+ Minecraft m = ModSettings.getMcinst();
+ m.displayGuiScreen(screen);
+ screen.setActive();
+ }
+
+ /**
+ * Show a screen - TWL widget version. This is the recommended way to show a
+ * TWL widget as a screen.
+ *
+ * @param screen
+ * widget to show - will be sized to size of screen when twl was
+ * started.
+ */
+ public static void show(Widget screen) {
+ GuiModScreen.show(new GuiModScreen(GuiModScreen.currentScreen, screen));
+ }
+
+ /**
+ * The type of background to draw. 0 is the default background. If you are
+ * in game, it will shade the screen grey before it draws your widget, like
+ * when you pause the game normally. If you are not in game, it will show
+ * the dirt background, like on the main menu. 1 is force the main menu
+ * (dirt) background. Anything else is no background, completely clear.
+ */
+ public int backgroundType = 0;
+ /**
+ * Actual main widget of this GuiModScreen
+ */
+ public Widget mainwidget;
+ /**
+ * Reference to parent screen, is used by GuiModScreen#back()
+ *
+ * @see GuiModScreen#back()
+ */
+ public GuiScreen parentScreen;
+
+ /**
+ * Only use this constructor from subclasses. does not take a widget, so
+ * that the subclass can build the widget before storing it. put your main
+ * widget in mainwidget, of course.
+ *
+ * @param screen
+ * parent screen
+ *
+ * @see GuiModScreen#back()
+ */
+ protected GuiModScreen(GuiScreen screen) {
+ parentScreen = screen;
+ GuiModScreen.currentScreen = this;
+ allowUserInput = false;
+ }
+
+ /**
+ * main constructor, to be used if you are instantiating this class.
+ *
+ * @param screen
+ * parent screen - make sure this is right!
+ * @param widget
+ * main widget to display
+ */
+ public GuiModScreen(GuiScreen screen, Widget widget) {
+ mainwidget = widget;
+ parentScreen = screen;
+ GuiModScreen.currentScreen = this;
+ allowUserInput = false;
+ }
+
+ @Override
+ public void drawScreen(int var1, int var2, float var3) {
+ switch (backgroundType) {
+ case 0: {
+ drawDefaultBackground();
+ break;
+ }
+ case 1: {
+ drawBackground(0);
+ break;
+ }
+ default: {
+ break;
+ }
+ }
+ LWJGLRenderer var4 = (LWJGLRenderer) GuiWidgetScreen.getInstance().gui
+ .getRenderer();
+ ScaledResolution var5 = new ScaledResolution(
+ GuiWidgetScreen.getInstance().minecraftInstance.gameSettings,
+ GuiWidgetScreen.getInstance().minecraftInstance.displayWidth,
+ GuiWidgetScreen.getInstance().minecraftInstance.displayHeight);
+ RenderScale.scale = var5.getScaleFactor();
+ var4.syncViewportSize();
+ GuiWidgetScreen.getInstance().gui.update();
+ }
+
+ @Override
+ public void handleInput() {
+ }
+
+ private void setActive() {
+ GuiWidgetScreen.getInstance().setScreen(mainwidget);
+ }
+}
diff --git a/mcp/sharose/mods/guiapi/GuiModSelect.java b/mcp/sharose/mods/guiapi/GuiModSelect.java
new file mode 100644
index 0000000..c4aecb0
--- /dev/null
+++ b/mcp/sharose/mods/guiapi/GuiModSelect.java
@@ -0,0 +1,41 @@
+package sharose.mods.guiapi;
+
+import net.minecraft.client.gui.GuiScreen;
+
+/**
+ * This is a Subclass of GuiModScreen, and acts as the entry point from the
+ * button in the options menu. This is just used internally. It is also where
+ * the code to enable MLProps edting is.
+ *
+ * @author lahwran
+ * @author ShaRose
+ */
+public class GuiModSelect extends GuiModScreen {
+ static {
+
+ }
+
+ @SuppressWarnings("unused")
+ private static void selectScreen(Integer i) {
+ GuiModScreen.show(ModSettingScreen.modScreens.get(i).theWidget);
+ GuiModScreen.clicksound();
+ }
+
+ protected GuiModSelect(GuiScreen screen) {
+ super(screen);
+ WidgetClassicTwocolumn w = new WidgetClassicTwocolumn();
+ w.verticalPadding = 10;
+ for (int i = 0; i < ModSettingScreen.modScreens.size(); i++) {
+ ModSettingScreen m = ModSettingScreen.modScreens.get(i);
+ w.add(GuiApiHelper
+ .makeButton(m.buttonTitle, "selectScreen",
+ GuiModSelect.class, false,
+ new Class[] { Integer.class }, i));
+ }
+ WidgetSimplewindow mainwidget = new WidgetSimplewindow(w,
+ "Select a Mod");
+ mainwidget.hPadding = 0;
+ mainwidget.mainWidget.setTheme("scrollpane-notch");
+ this.mainwidget = mainwidget;
+ }
+}
diff --git a/mcp/sharose/mods/guiapi/GuiWidgetScreen.java b/mcp/sharose/mods/guiapi/GuiWidgetScreen.java
new file mode 100644
index 0000000..6716b72
--- /dev/null
+++ b/mcp/sharose/mods/guiapi/GuiWidgetScreen.java
@@ -0,0 +1,169 @@
+package sharose.mods.guiapi;
+
+import java.io.IOException;
+import java.net.URL;
+import java.net.URLConnection;
+import java.net.URLStreamHandler;
+
+import org.lwjgl.opengl.Util;
+
+import net.minecraft.client.Minecraft;
+import net.minecraft.client.gui.ScaledResolution;
+import de.matthiasmann.twl.GUI;
+import de.matthiasmann.twl.Widget;
+import de.matthiasmann.twl.input.lwjgl.LWJGLInput;
+import de.matthiasmann.twl.renderer.lwjgl.LWJGLRenderer;
+import de.matthiasmann.twl.theme.ThemeManager;
+
+/**
+ * TWL Widget that switches out child widgets. the Minecraft gui end and twl
+ * rendering is managed from GuiModScreen.
+ *
+ * @author lahwran
+ */
+public class GuiWidgetScreen extends Widget {
+ /**
+ * The initialized instance of GuiWidgetScreen.
+ */
+ public static GuiWidgetScreen instance;
+ /**
+ * The height of the screen that the widget will render on.
+ */
+ public static int screenheight;
+ /**
+ * The width of the screen that the widget will render on.
+ */
+ public static int screenwidth;
+
+ public static URL themeURL;
+
+ /**
+ * get the instance of GuiWidget, creating it if needed
+ *
+ * @return GuiWidgetScreen singleton
+ */
+ public static GuiWidgetScreen getInstance() {
+ if (GuiWidgetScreen.instance != null) {
+ return GuiWidgetScreen.instance;
+ }
+
+ try {
+ Util.checkGLError();
+ GuiWidgetScreen.instance = new GuiWidgetScreen();
+ Util.checkGLError();
+ GuiWidgetScreen.instance.renderer = new LWJGLRenderer();
+ Util.checkGLError();
+ String themename = "twlGuiTheme.xml";
+ Util.checkGLError();
+ GuiWidgetScreen.instance.gui = new GUI(GuiWidgetScreen.instance,
+ GuiWidgetScreen.instance.renderer, new LWJGLInput());
+ Util.checkGLError();
+ GuiWidgetScreen.themeURL = new URL("classloader","",-1,themename,new URLStreamHandler(){
+ @Override
+ protected URLConnection openConnection(URL paramURL) throws IOException {
+ String file = paramURL.getFile();
+ if(file.startsWith("/")) { file = file.substring(1); }
+ return GuiWidgetScreen.class.getClassLoader().getResource(file).openConnection();
+ }
+ });
+ Util.checkGLError();
+ //GuiWidgetScreen.themeURL = new URL("file:\\G:\\MineCraft\\GitHub\\GuiAPI\\theme\\twlGuiTheme.xml");
+ Util.checkGLError();
+ GuiWidgetScreen.instance.theme = ThemeManager
+ .createThemeManager(GuiWidgetScreen.themeURL,
+ GuiWidgetScreen.instance.renderer);
+ Util.checkGLError();
+ if (GuiWidgetScreen.instance.theme == null) {
+ throw new RuntimeException(
+ "I don't think you installed the theme correctly ...");
+ }
+ GuiWidgetScreen.instance.setTheme("");
+ GuiWidgetScreen.instance.gui
+ .applyTheme(GuiWidgetScreen.instance.theme);
+ GuiWidgetScreen.instance.minecraftInstance = ModSettings
+ .getMcinst();
+ GuiWidgetScreen.instance.screenSize = new ScaledResolution(
+ GuiWidgetScreen.instance.minecraftInstance.gameSettings,
+ GuiWidgetScreen.instance.minecraftInstance.displayWidth,
+ GuiWidgetScreen.instance.minecraftInstance.displayHeight);
+ } catch (Throwable e) {
+ e.printStackTrace();
+ RuntimeException e2 = new RuntimeException("error loading theme");
+ e2.initCause(e);
+ throw e2;
+ }
+ return GuiWidgetScreen.instance;
+ }
+
+ /**
+ * The widget that is currently displayed.
+ */
+ public Widget currentWidget = null;
+ /**
+ * This is a reference to a TWL class that is used to render the widgets.
+ */
+ public GUI gui = null;
+ /**
+ * This is a reference to Minecraft.
+ */
+ public Minecraft minecraftInstance;
+ /**
+ * This is the rendered used by TWL.
+ */
+ public LWJGLRenderer renderer = null;
+ /**
+ * This is the ScaledResolution class that is used to scale all of the
+ * widgets.
+ */
+ public ScaledResolution screenSize = null;
+ /**
+ * This the the ThemeManager for GuiAPI.
+ */
+ public ThemeManager theme = null;
+
+ /**
+ * This creates a new instance of GuiWidgetScreen. It should only be used
+ * internally. Please use the static method getInstance() instead.
+ */
+ public GuiWidgetScreen() {
+ }
+
+ @Override
+ public void layout() {
+ screenSize = new ScaledResolution(minecraftInstance.gameSettings,
+ minecraftInstance.displayWidth, minecraftInstance.displayHeight);
+ if (currentWidget != null) {
+ GuiWidgetScreen.screenwidth = screenSize.getScaledWidth();
+ GuiWidgetScreen.screenheight = screenSize.getScaledHeight();
+ currentWidget.setSize(GuiWidgetScreen.screenwidth,
+ GuiWidgetScreen.screenheight);
+ currentWidget.setPosition(0, 0);
+ }
+ }
+
+ /**
+ * Removes all children and clears the current widget.
+ */
+ public void resetScreen() {
+ removeAllChildren();
+ currentWidget = null;
+ }
+
+ /**
+ * to be called only from GuiModScreen, sets the widget to display.
+ * GuiModScreen manages this.
+ *
+ * @param widget
+ * widget to display
+ */
+ public void setScreen(Widget widget) {
+ gui.resyncTimerAfterPause();
+ gui.clearKeyboardState();
+ gui.clearMouseState();
+ removeAllChildren();
+ add(widget);
+ GuiApiFontHelper.resyncCustomFonts();
+
+ currentWidget = widget;
+ }
+}
diff --git a/mcp/sharose/mods/guiapi/IWidgetAlwaysDraw.java b/mcp/sharose/mods/guiapi/IWidgetAlwaysDraw.java
new file mode 100644
index 0000000..d7ffdc4
--- /dev/null
+++ b/mcp/sharose/mods/guiapi/IWidgetAlwaysDraw.java
@@ -0,0 +1,5 @@
+package sharose.mods.guiapi;
+
+public interface IWidgetAlwaysDraw {
+
+}
diff --git a/mcp/sharose/mods/guiapi/ModAction.java b/mcp/sharose/mods/guiapi/ModAction.java
new file mode 100644
index 0000000..fe3c4ae
--- /dev/null
+++ b/mcp/sharose/mods/guiapi/ModAction.java
@@ -0,0 +1,304 @@
+package sharose.mods.guiapi;
+
+import java.beans.PropertyChangeEvent;
+import java.beans.PropertyChangeListener;
+import java.lang.reflect.Method;
+import java.security.InvalidParameterException;
+import java.util.ArrayList;
+
+import de.matthiasmann.twl.CallbackWithReason;
+import de.matthiasmann.twl.ListBox;
+import de.matthiasmann.twl.ListBox.CallbackReason;
+import de.matthiasmann.twl.TextArea;
+
+/**
+ * This class is a helper designed to make it easier to use callbacks. It
+ * implements Runnable and PropertyChangeListener, and you can use it in several
+ * ways.
+ *
+ * @author _303
+ * @author ShaRose
+ */
+@SuppressWarnings("rawtypes")
+public class ModAction implements Runnable, PropertyChangeListener,
+ TextArea.Callback, CallbackWithReason {
+ @SuppressWarnings("unchecked")
+ private static Boolean checkArguments(Class[] classTypes, Object[] arguments) {
+ if (classTypes.length != arguments.length) {
+ return false;
+ }
+ for (int i = 0; i < classTypes.length; i++) {
+ if (!classTypes[i].isAssignableFrom(arguments[i].getClass())) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ private Object[] defaultArguments;
+
+ private ArrayList mergedActions = new ArrayList();
+ private String methodName;
+ private Class[] methodParams = new Class[0];
+ private Object objectRef;
+ private Object tag;
+
+ /**
+ * This is the most common ModAction constructor. You simply specify what
+ * has the method you want, and the method's name and (Optionally) the
+ * parameters.
+ *
+ * @param o
+ * The object reference or class that contains the method you
+ * wish to call.
+ * @param method
+ * The name of the method you wish to call.
+ * @param params
+ * The parameters of the method you wish to call.
+ */
+ public ModAction(Object o, String method, Class... params) {
+ setTag(method);
+ methodParams = params;
+ setupHandler(o, method);
+ }
+
+ /**
+ * This is an overload to allow the nameRef string.
+ *
+ * @param o
+ * The object reference or class that contains the method you
+ * wish to call.
+ * @param method
+ * The name of the method you wish to call.
+ * @param name
+ * The name of this ModAction. Something else you can use to keep
+ * track, and this is included within exceptions.
+ * @param params
+ * The parameters of the method you wish to call.
+ */
+ public ModAction(Object o, String method, String name, Class... params) {
+ this(o, method, params);
+ setTag(name);
+ }
+
+ /**
+ * This is a constructor that is only supposed to be used internally. It
+ * sets no actual handler, only a name.
+ *
+ * @param name
+ * The name to use for this ModAction.
+ */
+ private ModAction(String name) {
+ setTag(name);
+ }
+
+ /**
+ * Call this ModAction and any Merged actions with the provided arguments.
+ * If the arguments do not match it will try using the default arguments, if
+ * they exist. If not, it will throw an exception.
+ *
+ * @param args
+ * The arguments to try and call for the ModAction (And any
+ * merged ModActions)
+ * @return The return values for each ModAction.
+ * @throws Exception
+ * Any exception thrown when the ModAction attempts to run.
+ */
+ public Object[] call(Object... args) throws Exception {
+ try {
+ if (mergedActions.isEmpty()) {
+ return new Object[] { callInternal(args) };
+ }
+ Object[] returnvals = new Object[mergedActions.size()];
+ for (int i = 0; i < returnvals.length; i++) {
+ returnvals[i] = mergedActions.get(i).call(args);
+ }
+ return returnvals;
+ } catch (Exception e) {
+ e.printStackTrace();
+ throw new Exception("error calling callback '" + getTag() + "'.", e);
+ }
+ }
+
+ @Override
+ public void callback(CallbackReason reason) {
+ if ((methodParams.length != 1)
+ || (methodParams[0] != CallbackReason.class)) {
+ throw new RuntimeException(
+ "invalid method parameters for a CallbackWithReason callback. Modaction is '"
+ + getTag() + "'.");
+ }
+ try {
+ call(reason);
+ } catch (Exception e) {
+ e.printStackTrace();
+ throw new RuntimeException(
+ "Error when calling CallbackWithReason callback. Modaction is '"
+ + getTag() + "'.", e);
+ }
+ }
+
+ private Object callInternal(Object... args) throws Exception {
+ if (!ModAction.checkArguments(methodParams, args)) {
+ if (defaultArguments != null) {
+ args = defaultArguments;
+ }
+ }
+ try {
+ Method meth = getMethodRecursively(objectRef, methodName);
+ return meth.invoke(objectRef instanceof Class ? null : objectRef,
+ args);
+ } catch (Exception e) {
+ throw new Exception("error calling callback '" + getTag() + "'.", e);
+ }
+ }
+
+ private Method getMethodRecursively(Object o, String method)
+ throws Exception {
+ Class> currentclass = (o instanceof Class ? (Class>) o : o
+ .getClass());
+ while (true) {
+ if (currentclass == null) {
+ throw new Exception(
+ "Unable to locate method '"
+ + method
+ + "' anywhere in the inheritance chain of object '"
+ + (o instanceof Class ? (Class>) o : o
+ .getClass()).getName() + "'!");
+ }
+ try {
+ Method returnval = currentclass.getDeclaredMethod(method,
+ methodParams);
+ if (returnval != null) {
+ returnval.setAccessible(true);
+ return returnval;
+ }
+ } catch (Throwable x) {
+ }
+ currentclass = currentclass.getSuperclass();
+ }
+ }
+
+ /**
+ * @return The tag of this ModAction.
+ */
+ public Object getTag() {
+ return tag;
+ }
+
+ @Override
+ public void handleLinkClicked(String link) {
+ if ((methodParams.length != 1) || (methodParams[0] != String.class)) {
+ throw new RuntimeException(
+ "invalid method parameters for a TextArea.Callback callback. Modaction is '"
+ + getTag() + "'.");
+ }
+ try {
+ call(link);
+ } catch (Exception e) {
+ e.printStackTrace();
+ throw new RuntimeException(
+ "Error when calling TextArea.Callback callback. Modaction is '"
+ + getTag() + "'.", e);
+ }
+ }
+
+ /**
+ * Merge[s] newAction[s] with this action.
+ *
+ * @param newActions
+ * The new Action[s] to merge with.
+ * @return The Merged ModAction.
+ */
+ public ModAction mergeAction(ModAction... newActions) {
+ if (mergedActions.isEmpty()) {
+ ModAction merged = new ModAction("Merged ModAction");
+ merged.mergedActions.add(this);
+ for (ModAction modAction : newActions) {
+ merged.mergedActions.add(modAction);
+ }
+
+ return merged;
+ }
+ for (ModAction modAction : newActions) {
+ mergedActions.add(modAction);
+ }
+ return this;
+ }
+
+ @Override
+ public void propertyChange(PropertyChangeEvent paramPropertyChangeEvent) {
+ if ((methodParams.length != 1)
+ || (methodParams[0] != PropertyChangeEvent.class)) {
+ throw new RuntimeException(
+ "invalid method parameters for a PropertyChangeListener callback. Modaction is '"
+ + getTag() + "'.");
+ }
+ try {
+ call(paramPropertyChangeEvent);
+ } catch (Exception e) {
+ e.printStackTrace();
+ throw new RuntimeException(
+ "Error when calling PropertyChangeListener callback. Modaction is '"
+ + getTag() + "'.", e);
+ }
+ }
+
+ @Override
+ public void run() {
+ try {
+ call();
+ } catch (Exception e) {
+ e.printStackTrace();
+ throw new RuntimeException(
+ "Error when calling Runnable callback. Modaction is '"
+ + getTag() + "'.", e);
+ }
+ }
+
+ /**
+ * Set the arguments to use if no or invalid arguments are provided. Throws
+ * InvalidParameterException if the arguments provided do not match the
+ * method parameters, or are not assignable to those types.
+ *
+ * @param Arguments
+ * The arguments to try and call.
+ * @return this
+ */
+ public ModAction setDefaultArguments(Object... Arguments) {
+ if (!ModAction.checkArguments(methodParams, Arguments)) {
+ throw new InvalidParameterException(
+ "Arguments do not match the parameters.");
+ }
+ defaultArguments = Arguments;
+ return this;
+ }
+
+ /**
+ * Sets the tag of this ModAction. Used for tracking, and is included with
+ * exceptions.
+ *
+ * @param tag
+ * The tag to assign to this ModAction.
+ */
+ public void setTag(Object tag) {
+ this.tag = tag;
+ }
+
+ private void setupHandler(Object o, String method) {
+ try {
+ getMethodRecursively(o, method);
+ } catch (Exception e) {
+ e.printStackTrace();
+ throw new RuntimeException(
+ "Could not locate Method with included information.", e);
+ }
+ methodName = method;
+ objectRef = o;
+ }
+
+ @Override
+ public String toString() {
+ return "ModAction [methodName=" + methodName + ", tag=" + tag + "]";
+ }
+}
\ No newline at end of file
diff --git a/mcp/sharose/mods/guiapi/ModSettingScreen.java b/mcp/sharose/mods/guiapi/ModSettingScreen.java
new file mode 100644
index 0000000..cd98e7e
--- /dev/null
+++ b/mcp/sharose/mods/guiapi/ModSettingScreen.java
@@ -0,0 +1,134 @@
+package sharose.mods.guiapi;
+
+import java.util.ArrayList;
+
+import de.matthiasmann.twl.Widget;
+
+/**
+ * This is the class that GuiModSelect uses to show Subscreens. Create one of
+ * these to create your own subscreen, from which you can add widgets to. This
+ * automatically registers the button on the Mod Setting Screen.
+ *
+ * @author lahwran
+ */
+public class ModSettingScreen {
+ /**
+ * The current context.
+ */
+ public static String guiContext = "";
+ /**
+ * The list of currently registered ModScreens.
+ */
+ public static ArrayList modScreens = new ArrayList();
+ /**
+ * title to show on button to this modscreen
+ */
+ public String buttonTitle;
+ /**
+ * name to show at top of screen
+ */
+ public String niceName;
+ /**
+ * the main widget to pass into GuiModScreen.show()
+ */
+ public Widget theWidget;
+ /**
+ * the column widget to show the child widgets in
+ */
+ public WidgetClassicTwocolumn widgetColumn;
+
+ /**
+ * convenience constructor for when you want to show the same name on the
+ * button and screen title
+ *
+ * @param name
+ * mod nice name
+ */
+ public ModSettingScreen(String name) {
+ this(name, name);
+ }
+
+ /**
+ * The main Constructor for ModSettingScreen. Creates a WidgetSimplewindow
+ * as the main Widget, sets the title for said WidgetSimplewindow, and
+ * registers this ModSettingScreen on the settings screen.
+ *
+ * @param nicename
+ * The title that will be on the WidgetSimplewindow.
+ * @param buttontitle
+ * button-to-screen title
+ */
+ public ModSettingScreen(String nicename, String buttontitle) {
+ ModSettingScreen.modScreens.add(this);
+ buttonTitle = buttontitle;
+ niceName = nicename;
+ widgetColumn = new WidgetClassicTwocolumn();
+ theWidget = new WidgetSimplewindow(widgetColumn, niceName);
+ }
+
+ /**
+ * An alternate Constructor for ModSettingScreen. Instead of creating a
+ * WidgetSimplewindow, this simply lets you pass a widget of your choosing.
+ *
+ * @param widget
+ * The main widget you want to use for this ModSettingScreen.
+ * @param buttontitle
+ * button-to-screen title
+ */
+ public ModSettingScreen(Widget widget, String buttontitle) {
+ ModSettingScreen.modScreens.add(this);
+ buttonTitle = buttontitle;
+ theWidget = widget;
+ }
+
+ /**
+ * Add a widget
+ *
+ * @param newwidget
+ * the widget to add
+ */
+ public void append(Widget newwidget) {
+ if (widgetColumn != null) {
+ widgetColumn.add(newwidget);
+ } else {
+ theWidget.add(newwidget);
+ }
+ }
+
+ /**
+ * Remove a widget
+ *
+ * @param child
+ * widget to remove
+ */
+ public void remove(Widget child) {
+ if (widgetColumn != null) {
+ widgetColumn.removeChild(child);
+ } else {
+ theWidget.removeChild(child);
+ }
+ }
+
+ /**
+ * Changes the widgetColumn to or from WidgetClassicTwocolumn or
+ * WidgetSinglecolumn.
+ *
+ * @param value
+ * What to change it to. True for WidgetSinglecolumn, False for
+ * WidgetClassicTwocolumn.
+ */
+ public void setSingleColumn(Boolean value) {
+ Boolean isSingle = WidgetSinglecolumn.class.isInstance(widgetColumn);
+ if (isSingle == value) {
+ return;
+ }
+ WidgetClassicTwocolumn w2 = (value ? new WidgetSinglecolumn()
+ : new WidgetClassicTwocolumn());
+ for (int i = 0; i < widgetColumn.getNumChildren(); i++) {
+ w2.add(widgetColumn.getChild(i));
+ }
+ widgetColumn = w2;
+ theWidget = new WidgetSimplewindow(widgetColumn,
+ ((WidgetSimplewindow) theWidget).titleWidget.getText());
+ }
+}
diff --git a/mcp/sharose/mods/guiapi/ModSettings.java b/mcp/sharose/mods/guiapi/ModSettings.java
new file mode 100644
index 0000000..e9808c1
--- /dev/null
+++ b/mcp/sharose/mods/guiapi/ModSettings.java
@@ -0,0 +1,1022 @@
+package sharose.mods.guiapi;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.lang.reflect.Field;
+import java.security.InvalidParameterException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Properties;
+
+import net.minecraft.client.Minecraft;
+import de.matthiasmann.twl.Widget;
+
+/**
+ * Main interface class for Settings API
+ *
+ * @author lahwran
+ */
+public class ModSettings {
+ /**
+ * A list of all ModSettings instances.
+ */
+ public static ArrayList all = new ArrayList();
+ /**
+ * A map of context names and the directories they save to.
+ */
+ public static HashMap contextDatadirs;
+ /**
+ * The current context.
+ */
+ public static String currentContext;
+ /**
+ * Debug mode flag. Should always be false.
+ */
+ public static final boolean debug = false;
+
+ static {
+ ModSettings.contextDatadirs = new HashMap();
+ ModSettings.currentContext = "";
+ ModSettings.contextDatadirs.put("", "mods");
+ }
+
+ /**
+ * Debug printer. This prints to System.out, and only works when in debug
+ * mode.
+ *
+ * @param s
+ * The string to output.
+ */
+ public static void dbgout(String s) {
+ if (ModSettings.debug) {
+ System.out.println(s);
+ }
+ }
+
+ /**
+ * Returns, and creates if needed, an application directory.
+ *
+ * @param app
+ * The name of the application.
+ * @return A File reference to the directory created.
+ */
+ public static File getAppDir(String app) {
+ try {
+ return new File(Minecraft.getMinecraftDir(), app)
+ .getCanonicalFile(); // Attempt to clean it up a bit.
+ } catch (IOException e) {
+ // If it can't be cleaned for whatever reason, just return the
+ // 'unclean' path. Normally I would just add throws, but that might
+ // break other mods.
+ return new File(Minecraft.getMinecraftDir(), app);
+ }
+ }
+
+ /**
+ * This finds and returns a Minecraft instance. It caches it if it has
+ * already been located.
+ *
+ * @return The minecraft instance.
+ */
+ public static Minecraft getMcinst() {
+ return Minecraft.getMinecraft();
+ }
+
+ /**
+ * Loads all saved settings for a specific context.
+ *
+ * @param context
+ * The context to load from.
+ */
+ public static void loadAll(String context) {
+ for (int i = 0; i < ModSettings.all.size(); i++) {
+ ModSettings.all.get(i).load(context);
+ }
+ }
+
+ /**
+ * Sets the context for mods. This means you can specify a context on a per
+ * world / per server basis, or anything else you would prefer. This will
+ * carry thoughout all mods.
+ *
+ * @param name
+ * The name reference of the context.
+ * @param location
+ * The location that this context stores and loads data from.
+ */
+ public static void setContext(String name, String location) {
+ if (name != null) {
+ ModSettings.contextDatadirs.put(name, location);
+ ModSettings.currentContext = name;
+ if (!name.equals("")) {
+ ModSettings.loadAll(ModSettings.currentContext);
+ }
+ } else {
+ ModSettings.currentContext = "";
+ }
+ }
+
+ /**
+ * Mod name as used in .minecraft/mods/${modbackendname}/
+ */
+ public String backendname;
+ /**
+ * all registered settings for this mod
+ */
+ @SuppressWarnings("rawtypes")
+ public ArrayList Settings;
+ /**
+ * Whether or not Settings have been loaded for this mod.
+ */
+ public boolean settingsLoaded = false;
+
+ /**
+ * @param modbackendname
+ * used to initialize class modbackendname field
+ */
+ @SuppressWarnings("rawtypes")
+ public ModSettings(String modbackendname) {
+ backendname = modbackendname;
+ Settings = new ArrayList();
+ ModSettings.all.add(this);
+ }
+
+ /**
+ * convenience boolean setting adder
+ */
+ public SettingBoolean addSetting(ModSettingScreen screen, String nicename,
+ String backendname, boolean value) {
+ SettingBoolean s = new SettingBoolean(backendname, value);
+ WidgetBoolean w = new WidgetBoolean(s, nicename);
+ screen.append(w);
+ append(s);
+ return s;
+ }
+
+ /**
+ * convenience boolean setting adder
+ */
+ public SettingBoolean addSetting(ModSettingScreen screen, String nicename,
+ String backendname, boolean value, String truestring,
+ String falsestring) {
+ SettingBoolean s = new SettingBoolean(backendname, value);
+ WidgetBoolean w = new WidgetBoolean(s, nicename, truestring,
+ falsestring);
+ screen.append(w);
+ append(s);
+ return s;
+ }
+
+ /**
+ * convenience float setting adder
+ */
+ public SettingFloat addSetting(ModSettingScreen screen, String nicename,
+ String backendname, float value) {
+ SettingFloat s = new SettingFloat(backendname, value);
+ WidgetFloat w = new WidgetFloat(s, nicename);
+ screen.append(w);
+ append(s);
+ return s;
+ }
+
+ /**
+ * convenience float setting adder
+ */
+ public SettingFloat addSetting(ModSettingScreen screen, String nicename,
+ String backendname, float value, float min, float step, float max) {
+ SettingFloat s = new SettingFloat(backendname, value, min, step, max);
+ WidgetFloat w = new WidgetFloat(s, nicename);
+ screen.append(w);
+ append(s);
+ return s;
+ }
+
+ /**
+ * convenience key setting adder
+ */
+ public SettingKey addSetting(ModSettingScreen screen, String nicename,
+ String backendname, int value) {
+ SettingKey s = new SettingKey(backendname, value);
+ WidgetKeybinding w = new WidgetKeybinding(s, nicename);
+ screen.append(w);
+ append(s);
+ return s;
+ }
+
+ /**
+ * convenience int setting adder
+ */
+ public SettingInt addSetting(ModSettingScreen screen, String nicename,
+ String backendname, int value, int min, int max) {
+ SettingInt s = new SettingInt(backendname, value, min, 1, max);
+ WidgetInt w = new WidgetInt(s, nicename);
+ screen.append(w);
+ append(s);
+ return s;
+ }
+
+ /**
+ * convenience int setting adder
+ */
+ public SettingInt addSetting(ModSettingScreen screen, String nicename,
+ String backendname, int value, int min, int step, int max) {
+ SettingInt s = new SettingInt(backendname, value, min, step, max);
+ WidgetInt w = new WidgetInt(s, nicename);
+ screen.append(w);
+ append(s);
+ return s;
+ }
+
+ /**
+ * convenience multi setting adder
+ */
+ public SettingMulti addSetting(ModSettingScreen screen, String nicename,
+ String backendname, int value, String... labels) {
+ SettingMulti s = new SettingMulti(backendname, value, labels);
+ WidgetMulti w = new WidgetMulti(s, nicename);
+ screen.append(w);
+ append(s);
+ return s;
+ }
+
+ /**
+ * convenience text setting adder
+ */
+ public SettingText addSetting(ModSettingScreen screen, String nicename,
+ String backendname, String value) {
+ SettingText s = new SettingText(backendname, value);
+ WidgetText w = new WidgetText(s, nicename);
+ screen.append(w);
+ append(s);
+ return s;
+ }
+
+ /**
+ * convenience boolean setting adder
+ */
+ public SettingBoolean addSetting(Widget w2, String nicename,
+ String backendname, boolean value) {
+ SettingBoolean s = new SettingBoolean(backendname, value);
+ WidgetBoolean w = new WidgetBoolean(s, nicename);
+ w2.add(w);
+ append(s);
+ return s;
+ }
+
+ /**
+ * convenience boolean setting adder
+ */
+ public SettingBoolean addSetting(Widget w2, String nicename,
+ String backendname, boolean value, String truestring,
+ String falsestring) {
+ SettingBoolean s = new SettingBoolean(backendname, value);
+ WidgetBoolean w = new WidgetBoolean(s, nicename, truestring,
+ falsestring);
+ w2.add(w);
+ append(s);
+ return s;
+ }
+
+ /**
+ * convenience float setting adder
+ */
+ public SettingFloat addSetting(Widget w2, String nicename,
+ String backendname, float value) {
+ SettingFloat s = new SettingFloat(backendname, value);
+ WidgetFloat w = new WidgetFloat(s, nicename);
+ w2.add(w);
+ append(s);
+ return s;
+ }
+
+ /**
+ * convenience float setting adder
+ */
+ public SettingFloat addSetting(Widget w2, String nicename,
+ String backendname, float value, float min, float step, float max) {
+ SettingFloat s = new SettingFloat(backendname, value, min, step, max);
+ WidgetFloat w = new WidgetFloat(s, nicename);
+ w2.add(w);
+ append(s);
+ return s;
+ }
+
+ /**
+ * convenience key setting adder
+ */
+ public SettingKey addSetting(Widget w2, String nicename,
+ String backendname, int value) {
+ SettingKey s = new SettingKey(backendname, value);
+ WidgetKeybinding w = new WidgetKeybinding(s, nicename);
+ w2.add(w);
+ append(s);
+ return s;
+ }
+
+ /**
+ * convenience int setting adder
+ */
+ public SettingInt addSetting(Widget w2, String nicename,
+ String backendname, int value, int min, int max) {
+ SettingInt s = new SettingInt(backendname, value, min, 1, max);
+ WidgetInt w = new WidgetInt(s, nicename);
+ w2.add(w);
+ append(s);
+ return s;
+ }
+
+ /**
+ * convenience int setting adder
+ */
+ public SettingInt addSetting(Widget w2, String nicename,
+ String backendname, int value, int min, int step, int max) {
+ SettingInt s = new SettingInt(backendname, value, min, step, max);
+ WidgetInt w = new WidgetInt(s, nicename);
+ w2.add(w);
+ append(s);
+ return s;
+ }
+
+ /**
+ * convenience multi setting adder
+ */
+ public SettingMulti addSetting(Widget w2, String nicename,
+ String backendname, int value, String... labels) {
+ SettingMulti s = new SettingMulti(backendname, value, labels);
+ WidgetMulti w = new WidgetMulti(s, nicename);
+ w2.add(w);
+ append(s);
+ return s;
+ }
+
+ /**
+ * convenience list setting adder
+ */
+ public SettingList addSetting(Widget w2, String nicename,
+ String backendname, String... options) {
+
+ ArrayList arrayList = new ArrayList();
+
+ for (int i = 0; i < options.length; i++) {
+ arrayList.add(options[i]);
+ }
+
+ SettingList s = new SettingList(backendname, arrayList);
+ WidgetList w = new WidgetList(s, nicename);
+ w2.add(w);
+ append(s);
+ return s;
+ }
+
+ /**
+ * convenience text setting adder
+ */
+ public SettingText addSetting(Widget w2, String nicename,
+ String backendname, String value) {
+ SettingText s = new SettingText(backendname, value);
+ WidgetText w = new WidgetText(s, nicename);
+ w2.add(w);
+ append(s);
+ return s;
+ }
+
+ /**
+ * add a setting to be saved.
+ *
+ * @param s
+ * setting to add - sets s.parent as well, don't add a setting to
+ * more than one modsettings
+ */
+ @SuppressWarnings({ "unchecked", "rawtypes" })
+ public void append(Setting s) {
+ Settings.add(s);
+ s.parent = this;
+ }
+
+ /**
+ * Copies the saved settings from one context to another.
+ *
+ * @param src
+ * The source context from which to copy.
+ * @param dest
+ * The destination context to save to.
+ */
+ public void copyContextAll(String src, String dest) {
+ for (int i = 0; i < Settings.size(); i++) {
+ Settings.get(i).copyContext(src, dest);
+ }
+ }
+
+ /**
+ * Get a list of all Boolean settings for the current context.
+ *
+ * @return The list of settings.
+ */
+ public ArrayList getAllBooleanSettings() {
+ return getAllBooleanSettings(ModSettings.currentContext);
+ }
+
+ /**
+ * Get a list of all Boolean settings for the specified context.
+ *
+ * @param context
+ * The context from which to copy from.
+ * @return The list of settings.
+ */
+ @SuppressWarnings("rawtypes")
+ public ArrayList getAllBooleanSettings(String context) {
+ ArrayList settings = new ArrayList();
+ for (Setting setting : Settings) {
+ if (!SettingBoolean.class.isAssignableFrom(setting.getClass())) {
+ continue;
+ }
+ settings.add((SettingBoolean) setting);
+ }
+ return settings;
+ }
+
+ /**
+ * Get a list of all Float settings for the current context.
+ *
+ * @return The list of settings.
+ */
+ public ArrayList getAllFloatSettings() {
+ return getAllFloatSettings(ModSettings.currentContext);
+ }
+
+ /**
+ * Get a list of all Float settings for the specified context.
+ *
+ * @param context
+ * The context from which to copy from.
+ * @return The list of settings.
+ */
+ @SuppressWarnings("rawtypes")
+ public ArrayList getAllFloatSettings(String context) {
+ ArrayList settings = new ArrayList();
+ for (Setting setting : Settings) {
+ if (!SettingFloat.class.isAssignableFrom(setting.getClass())) {
+ continue;
+ }
+ settings.add((SettingFloat) setting);
+ }
+ return settings;
+ }
+
+ /**
+ * Get a list of all Int settings for the current context.
+ *
+ * @return The list of settings.
+ */
+ public ArrayList getAllIntSettings() {
+ return getAllIntSettings(ModSettings.currentContext);
+ }
+
+ /**
+ * Get a list of all Int settings for the specified context.
+ *
+ * @param context
+ * The context from which to copy from.
+ * @return The list of settings.
+ */
+ @SuppressWarnings("rawtypes")
+ public ArrayList getAllIntSettings(String context) {
+ ArrayList settings = new ArrayList();
+ for (Setting setting : Settings) {
+ if (!SettingInt.class.isAssignableFrom(setting.getClass())) {
+ continue;
+ }
+ settings.add((SettingInt) setting);
+ }
+ return settings;
+ }
+
+ /**
+ * Get a list of all Key settings for the current context.
+ *
+ * @return The list of settings.
+ */
+ public ArrayList getAllKeySettings() {
+ return getAllKeySettings(ModSettings.currentContext);
+ }
+
+ /**
+ * Get a list of all Key settings for the specified context.
+ *
+ * @param context
+ * The context from which to copy from.
+ * @return The list of settings.
+ */
+ @SuppressWarnings("rawtypes")
+ public ArrayList getAllKeySettings(String context) {
+ ArrayList settings = new ArrayList();
+ for (Setting setting : Settings) {
+ if (!SettingKey.class.isAssignableFrom(setting.getClass())) {
+ continue;
+ }
+ settings.add((SettingKey) setting);
+ }
+ return settings;
+ }
+
+ /**
+ * Get a list of all Multi settings for the current context.
+ *
+ * @return The list of settings.
+ */
+ public ArrayList getAllMultiSettings() {
+ return getAllMultiSettings(ModSettings.currentContext);
+ }
+
+ /**
+ * Get a list of all Multi settings for the specified context.
+ *
+ * @param context
+ * The context from which to copy from.
+ * @return The list of settings.
+ */
+ @SuppressWarnings("rawtypes")
+ public ArrayList getAllMultiSettings(String context) {
+ ArrayList settings = new ArrayList();
+ for (Setting setting : Settings) {
+ if (!SettingMulti.class.isAssignableFrom(setting.getClass())) {
+ continue;
+ }
+ settings.add((SettingMulti) setting);
+ }
+ return settings;
+ }
+
+ /**
+ * Get a list of all Text settings for the current context.
+ *
+ * @return The list of settings.
+ */
+ public ArrayList getAllTextSettings() {
+ return getAllTextSettings(ModSettings.currentContext);
+ }
+
+ /**
+ * Get a list of all Text settings for the specified context.
+ *
+ * @param context
+ * The context from which to copy from.
+ * @return The list of settings.
+ */
+ @SuppressWarnings("rawtypes")
+ public ArrayList getAllTextSettings(String context) {
+ ArrayList settings = new ArrayList();
+ for (Setting setting : Settings) {
+ if (!SettingText.class.isAssignableFrom(setting.getClass())) {
+ continue;
+ }
+ settings.add((SettingText) setting);
+ }
+ return settings;
+ }
+
+ /**
+ * Gets the value of a setting by backend name from the current context.
+ *
+ * @param backendName
+ * The backend name of the setting.
+ * @return The boolean value.
+ */
+ public Boolean getBooleanSettingValue(String backendName) {
+ return getBooleanSettingValue(backendName, ModSettings.currentContext);
+ }
+
+ /**
+ * Gets the value of a setting by backend name from the specified context.
+ *
+ * @param backendName
+ * The backend name of the setting.
+ * @param context
+ * The context from which to copy from.
+ * @return The boolean value.
+ */
+ @SuppressWarnings("rawtypes")
+ public Boolean getBooleanSettingValue(String backendName, String context) {
+ return getSettingBoolean(backendName).get(context);
+ }
+
+ /**
+ * Gets the value of a setting by backend name from the current context.
+ *
+ * @param backendName
+ * The backend name of the setting.
+ * @return The Float value.
+ */
+ public Float getFloatSettingValue(String backendName) {
+ return getFloatSettingValue(backendName, ModSettings.currentContext);
+ }
+
+ /**
+ * Gets the value of a setting by backend name from the specified context.
+ *
+ * @param backendName
+ * The backend name of the setting.
+ * @param context
+ * The context from which to copy from.
+ * @return The Float value.
+ */
+ @SuppressWarnings("rawtypes")
+ public Float getFloatSettingValue(String backendName, String context) {
+ return getSettingFloat(backendName).get(context);
+ }
+
+ /**
+ * Gets the value of a setting by backend name from the current context.
+ *
+ * @param backendName
+ * The backend name of the setting.
+ * @return The Int value.
+ */
+ public Integer getIntSettingValue(String backendName) {
+ return getIntSettingValue(backendName, ModSettings.currentContext);
+ }
+
+ /**
+ * Gets the value of a setting by backend name from the specified context.
+ *
+ * @param backendName
+ * The backend name of the setting.
+ * @param context
+ * The context from which to copy from.
+ * @return The Int value.
+ */
+ @SuppressWarnings("rawtypes")
+ public Integer getIntSettingValue(String backendName, String context) {
+ return getSettingInt(backendName).get(context);
+ }
+
+ /**
+ * Gets the value of a setting by backend name from the current context.
+ *
+ * @param backendName
+ * The backend name of the setting.
+ * @return The Key value.
+ */
+ public Integer getKeySettingValue(String backendName) {
+ return getKeySettingValue(backendName, ModSettings.currentContext);
+ }
+
+ /**
+ * Gets the value of a setting by backend name from the specified context.
+ *
+ * @param backendName
+ * The backend name of the setting.
+ * @param context
+ * The context from which to copy from.
+ * @return The Key value.
+ */
+ @SuppressWarnings("rawtypes")
+ public Integer getKeySettingValue(String backendName, String context) {
+ return getSettingKey(backendName).get(context);
+ }
+
+ /**
+ * Gets the value of a setting by backend name from the current context.
+ *
+ * @param backendName
+ * The backend name of the setting.
+ * @return The text label for the value.
+ */
+ public String getMultiSettingLabel(String backendName) {
+ return getMultiSettingLabel(backendName, ModSettings.currentContext);
+ }
+
+ /**
+ * Gets the value of a setting by backend name from the specified context.
+ *
+ * @param backendName
+ * The backend name of the setting.
+ * @param context
+ * The context from which to copy from.
+ * @return The text label for the value.
+ */
+ @SuppressWarnings("rawtypes")
+ public String getMultiSettingLabel(String backendName, String context) {
+ SettingMulti setting = getSettingMulti(backendName);
+
+ return setting.labelValues[setting.get(context)];
+ }
+
+ /**
+ * Gets the value of a setting by backend name from the current context.
+ *
+ * @param backendName
+ * The backend name of the setting.
+ * @return The Multi value.
+ */
+ public Integer getMultiSettingValue(String backendName) {
+ return getMultiSettingValue(backendName, ModSettings.currentContext);
+ }
+
+ /**
+ * Gets the value of a setting by backend name from the specified context.
+ *
+ * @param backendName
+ * The backend name of the setting.
+ * @param context
+ * The context from which to copy from.
+ * @return The Multi value.
+ */
+ @SuppressWarnings("rawtypes")
+ public Integer getMultiSettingValue(String backendName, String context) {
+ return getSettingMulti(backendName).get(context);
+ }
+
+ /**
+ * Gets a setting by backend name.
+ *
+ * @param backendName
+ * The backend name of the setting.
+ * @return The SettingBoolean.
+ */
+ public SettingBoolean getSettingBoolean(String backendName) {
+ for (Setting setting : Settings) {
+ if (!SettingBoolean.class.isAssignableFrom(setting.getClass())) {
+ continue;
+ }
+ if (setting.backendName.equals(backendName)) {
+ return (SettingBoolean) setting;
+ }
+ }
+ throw new InvalidParameterException("SettingBoolean '" + backendName
+ + "' not found.");
+ }
+
+ /**
+ * Gets a setting by backend name.
+ *
+ * @param backendName
+ * The backend name of the setting.
+ * @return The SettingFloat.
+ */
+ public SettingFloat getSettingFloat(String backendName) {
+ for (Setting setting : Settings) {
+ if (!SettingFloat.class.isAssignableFrom(setting.getClass())) {
+ continue;
+ }
+ if (setting.backendName.equals(backendName)) {
+ return (SettingFloat) setting;
+ }
+ }
+ throw new InvalidParameterException("SettingFloat '" + backendName
+ + "' not found.");
+ }
+
+ /**
+ * Gets a setting by backend name.
+ *
+ * @param backendName
+ * The backend name of the setting.
+ * @return The SettingInt.
+ */
+ public SettingInt getSettingInt(String backendName) {
+ for (Setting setting : Settings) {
+ if (!SettingInt.class.isAssignableFrom(setting.getClass())) {
+ continue;
+ }
+ if (setting.backendName.equals(backendName)) {
+ return (SettingInt) setting;
+ }
+ }
+ throw new InvalidParameterException("SettingInt '" + backendName
+ + "' not found.");
+ }
+
+ /**
+ * Gets a setting by backend name.
+ *
+ * @param backendName
+ * The backend name of the setting.
+ * @return The SettingKey.
+ */
+ public SettingKey getSettingKey(String backendName) {
+ for (Setting setting : Settings) {
+ if (!SettingKey.class.isAssignableFrom(setting.getClass())) {
+ continue;
+ }
+ if (setting.backendName.equals(backendName)) {
+ return (SettingKey) setting;
+ }
+ }
+ throw new InvalidParameterException("SettingKey '" + backendName
+ + "' not found.");
+ }
+
+ /**
+ * Gets a setting by backend name.
+ *
+ * @param backendName
+ * The backend name of the setting.
+ * @return The SettingList.
+ */
+ public SettingDictionary getSettingList(String backendName) {
+ for (Setting setting : Settings) {
+ if (!SettingDictionary.class.isAssignableFrom(setting.getClass())) {
+ continue;
+ }
+ if (setting.backendName.equals(backendName)) {
+ return (SettingDictionary) setting;
+ }
+ }
+ throw new InvalidParameterException("SettingList '" + backendName
+ + "' not found.");
+ }
+
+ /**
+ * Gets a setting by backend name.
+ *
+ * @param backendName
+ * The backend name of the setting.
+ * @return The SettingMulti.
+ */
+ public SettingMulti getSettingMulti(String backendName) {
+ for (Setting setting : Settings) {
+ if (!SettingMulti.class.isAssignableFrom(setting.getClass())) {
+ continue;
+ }
+ if (setting.backendName.equals(backendName)) {
+ return (SettingMulti) setting;
+ }
+ }
+ throw new InvalidParameterException("SettingMulti '" + backendName
+ + "' not found.");
+ }
+
+ /**
+ * Gets a setting by backend name.
+ *
+ * @param backendName
+ * The backend name of the setting.
+ * @return The SettingText.
+ */
+ public SettingText getSettingText(String backendName) {
+ for (Setting setting : Settings) {
+ if (!SettingText.class.isAssignableFrom(setting.getClass())) {
+ continue;
+ }
+ if (setting.backendName.equals(backendName)) {
+ return (SettingText) setting;
+ }
+ }
+ throw new InvalidParameterException("SettingText '" + backendName
+ + "' not found.");
+ }
+
+ /**
+ * Gets the value of a setting by backend name from the current context.
+ *
+ * @param backendName
+ * The backend name of the setting.
+ * @return The Text value.
+ */
+ public String getTextSettingValue(String backendName) {
+ return getTextSettingValue(backendName, ModSettings.currentContext);
+ }
+
+ /**
+ * Gets the value of a setting by backend name from the specified context.
+ *
+ * @param backendName
+ * The backend name of the setting.
+ * @param context
+ * The context from which to copy from.
+ * @return The Text value.
+ */
+ @SuppressWarnings("rawtypes")
+ public String getTextSettingValue(String backendName, String context) {
+ return getSettingText(backendName).get(context);
+ }
+
+ /**
+ * Loads the settings for the default context.
+ */
+ public void load() {
+ load("");
+ settingsLoaded = true;
+ }
+
+ /**
+ * must be called after all settings are added for loading/saving to work.
+ * loads from .minecraft/mods/$backendname/guiconfig.properties if it
+ * exists. coming soon: set name of config file
+ *
+ * @param context
+ * The context to load from.
+ */
+ @SuppressWarnings("rawtypes")
+ public void load(String context) {
+ for (;;) {
+ try {
+ if (ModSettings.contextDatadirs.get(context) == null) {
+ break;
+ }
+ File path = ModSettings.getAppDir("/"
+ + ModSettings.contextDatadirs.get(context) + "/"
+ + backendname + "/");
+ if (!path.exists()) {
+ break;
+ }
+ File file = new File(path, "guiconfig.properties");
+ if (!file.exists()) {
+ break;
+ }
+ Properties p = new Properties();
+ p.load(new FileInputStream(file));
+ for (int i = 0; i < Settings.size(); i++) {
+ ModSettings.dbgout("setting load");
+ Setting z = Settings.get(i);
+ if (p.containsKey(z.backendName)) {
+ ModSettings.dbgout("setting "
+ + (String) p.get(z.backendName));
+ z.fromString((String) p.get(z.backendName), context);
+ }
+ }
+ break;
+ } catch (Exception e) {
+ System.out.println(e);
+ break;
+ }
+ }
+ }
+
+ /**
+ * removes a setting using ArrayList.remove
+ *
+ * @param s
+ * setting to remove
+ */
+ @SuppressWarnings({ "unchecked", "rawtypes" })
+ public void remove(Setting s) {
+ Settings.remove(s);
+ s.parent = null;
+ }
+
+ /**
+ * Resets all settings for the current context.
+ */
+ public void resetAll() {
+ resetAll(ModSettings.currentContext);
+ }
+
+ /**
+ * Resets all settings for the specified context.
+ *
+ * @param context
+ * The context to reset.
+ */
+ public void resetAll(String context) {
+ for (int i = 0; i < Settings.size(); i++) {
+ Settings.get(i).reset(context);
+ }
+ }
+
+ /**
+ * called every time a setting is changed saves settings file to
+ * .minecraft/mods/$backendname/guiconfig.properties coming soon: set name
+ * of config file
+ *
+ * @param context
+ * The context to save.
+ */
+ @SuppressWarnings("rawtypes")
+ public void save(String context) {
+ if (!settingsLoaded) {
+ return;
+ }
+ try {
+ File path = ModSettings.getAppDir("/"
+ + ModSettings.contextDatadirs.get(context) + "/"
+ + backendname + "/");
+ ModSettings.dbgout("saving context " + context + " ("
+ + path.getAbsolutePath() + " ["
+ + ModSettings.contextDatadirs.get(context) + "])");
+ if (!path.exists()) {
+ path.mkdirs();
+ }
+ File file = new File(path, "guiconfig.properties");
+ Properties p = new Properties();
+ for (int i = 0; i < Settings.size(); i++) {
+ Setting z = Settings.get(i);
+ p.put(z.backendName, z.toString(context));
+ }
+ FileOutputStream out = new FileOutputStream(file);
+ p.store(out, "");
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+
+ /**
+ * @return number of settings registered
+ */
+ public int size() {
+ return Settings.size();
+ }
+}
diff --git a/mcp/sharose/mods/guiapi/Setting.java b/mcp/sharose/mods/guiapi/Setting.java
new file mode 100644
index 0000000..a3f2db9
--- /dev/null
+++ b/mcp/sharose/mods/guiapi/Setting.java
@@ -0,0 +1,120 @@
+package sharose.mods.guiapi;
+
+import java.util.HashMap;
+
+import de.matthiasmann.twl.Widget;
+
+/**
+ * This is the base class for Settings.
+ *
+ * @author lahwran
+ * @param
+ * The type that this Setting will use.
+ */
+public abstract class Setting extends Widget {
+ /**
+ * The name used by ModSettings to save and load the setting.
+ */
+ public String backendName;
+ /**
+ * The default value for this setting.
+ */
+ public T defaultValue;
+ /**
+ * A reference to the Widget that displays this setting.
+ */
+ public WidgetSetting displayWidget = null;
+ /**
+ * Reference to the ModSettings this Setting is a child of.
+ */
+ public ModSettings parent = null;
+ /**
+ * value. do not write directly if you want things to update!
+ */
+ public HashMap values = new HashMap();
+
+ /**
+ * This is the basic constructor for Setting. Internal use only.
+ */
+ public Setting() {
+ }
+
+ /**
+ * Copies a setting from the source context to the destination context.
+ *
+ * @param srccontext
+ * The source context to copy data from.
+ * @param destcontext
+ * The destination context you would like to save the data to.
+ */
+ public void copyContext(String srccontext, String destcontext) {
+ values.put(destcontext, values.get(srccontext));
+ }
+
+ /**
+ * load back a string from toString()
+ */
+ public abstract void fromString(String s, String context);
+
+ /**
+ * Returns the setting for the current context.
+ *
+ * @return The setting.
+ */
+ public T get() {
+ return get(ModSettings.currentContext);
+ }
+
+ /**
+ * Returns the setting for the specified context.
+ *
+ * @param context
+ * The context to retrieve from.
+ * @return The setting.
+ */
+ public abstract T get(String context);
+
+ /**
+ * Resets this setting for the current context, saving the default and
+ * updating the display.
+ */
+ public void reset() {
+ reset(ModSettings.currentContext);
+ }
+
+ /**
+ * Resets this setting for the specified context, saving the default and
+ * updating the display.
+ *
+ * @param context
+ * The context to reset.
+ */
+ public void reset(String context) {
+ set(defaultValue, context);
+ }
+
+ /**
+ * Sets the value for this setting to the current context.
+ *
+ * @param v
+ * The value.
+ */
+ public void set(T v) {
+ set(v, ModSettings.currentContext);
+ }
+
+ /**
+ * Sets the value for this setting to the specified context.
+ *
+ * @param v
+ * The value.
+ * @param context
+ * The context to set.
+ */
+ public abstract void set(T v, String context);
+
+ /**
+ * return string to save, called from ModSettings.save()
+ */
+ public abstract String toString(String context);
+}
\ No newline at end of file
diff --git a/mcp/sharose/mods/guiapi/SettingBoolean.java b/mcp/sharose/mods/guiapi/SettingBoolean.java
new file mode 100644
index 0000000..0ac422b
--- /dev/null
+++ b/mcp/sharose/mods/guiapi/SettingBoolean.java
@@ -0,0 +1,68 @@
+package sharose.mods.guiapi;
+
+/**
+ * This is the Setting type for Booleans.
+ *
+ * @author lahwran
+ */
+public class SettingBoolean extends Setting {
+ /**
+ * This is the constructor for SettingBoolean. It sets the default value as
+ * false.
+ *
+ * @param name
+ * The backend name for this setting.
+ */
+ public SettingBoolean(String name) {
+ this(name, false);
+ }
+
+ /**
+ * This is the constructor for SettingBoolean.
+ *
+ * @param name
+ * The backend name for this setting.
+ * @param defValue
+ * The default value.
+ */
+ public SettingBoolean(String name, Boolean defValue) {
+ defaultValue = defValue;
+ values.put("", defaultValue);
+ backendName = name;
+ }
+
+ @Override
+ public void fromString(String s, String context) {
+ values.put(context, s.equals("true"));
+ if (displayWidget != null) {
+ displayWidget.update();
+ }
+ }
+
+ @Override
+ public Boolean get(String context) {
+ if (values.get(context) != null) {
+ return values.get(context);
+ } else if (values.get("") != null) {
+ return values.get("");
+ } else {
+ return defaultValue;
+ }
+ }
+
+ @Override
+ public void set(Boolean v, String context) {
+ values.put(context, v);
+ if (parent != null) {
+ parent.save(context);
+ }
+ if (displayWidget != null) {
+ displayWidget.update();
+ }
+ }
+
+ @Override
+ public String toString(String context) {
+ return (get(context) ? "true" : "false");
+ }
+}
\ No newline at end of file
diff --git a/mcp/sharose/mods/guiapi/SettingDictionary.java b/mcp/sharose/mods/guiapi/SettingDictionary.java
new file mode 100644
index 0000000..a67d062
--- /dev/null
+++ b/mcp/sharose/mods/guiapi/SettingDictionary.java
@@ -0,0 +1,77 @@
+package sharose.mods.guiapi;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.util.Properties;
+
+/**
+ * This is the Setting type for the Properties class. That is, a Dictionary of
+ * strings addressable by strings.
+ *
+ * @author ShaRose
+ */
+public class SettingDictionary extends Setting {
+
+ public SettingDictionary(String title) {
+ this(title, new Properties());
+ }
+
+ public SettingDictionary(String title, Properties defaultvalue) {
+ backendName = title;
+ defaultValue = defaultvalue;
+ values.put("", defaultvalue);
+ }
+
+ @Override
+ public void fromString(String s, String context) {
+ Properties prop = new Properties();
+ try {
+ prop.loadFromXML(new ByteArrayInputStream(s.getBytes("UTF-8")));
+ } catch (Throwable e) {
+ ModSettings.dbgout("Error reading SettingDictionary from context '"
+ + context + "': " + e);
+ }
+ values.put(context, prop);
+ if (displayWidget != null) {
+ displayWidget.update();
+ }
+ }
+
+ @Override
+ public Properties get(String context) {
+ if (values.get(context) != null) {
+ return values.get(context);
+ } else if (values.get("") != null) {
+ return values.get("");
+ } else {
+ return defaultValue;
+ }
+ }
+
+ @Override
+ public void set(Properties v, String context) {
+ values.put(context, v);
+ if (parent != null) {
+ parent.save(context);
+ }
+ if (displayWidget != null) {
+ displayWidget.update();
+ }
+ }
+
+ @Override
+ public String toString(String context) {
+ try {
+ Properties prop = get(context);
+ ByteArrayOutputStream output = new ByteArrayOutputStream();
+ prop.storeToXML(output, "GuiAPI SettingDictionary: DO NOT EDIT.");
+ return output.toString("UTF-8");
+ } catch (IOException e) {
+ ModSettings.dbgout("Error writing SettingDictionary from context '"
+ + context + "': " + e);
+ return "";
+ }
+ }
+
+}
diff --git a/mcp/sharose/mods/guiapi/SettingFloat.java b/mcp/sharose/mods/guiapi/SettingFloat.java
new file mode 100644
index 0000000..3085a08
--- /dev/null
+++ b/mcp/sharose/mods/guiapi/SettingFloat.java
@@ -0,0 +1,130 @@
+package sharose.mods.guiapi;
+
+/**
+ * This is the Setting type for Floats.
+ *
+ * @author lahwran
+ */
+public class SettingFloat extends Setting {
+ /**
+ * The maximum value.
+ */
+ public float maximumValue;
+ /**
+ * The minimum value.
+ */
+ public float minimumValue;
+ /**
+ * The step value.
+ */
+ public float stepValue;
+
+ /**
+ * A constructor for SettingFloat. Defaults settings to default value of 0,
+ * range of 0.0-1.0, and a step of 0.1.
+ *
+ * @param title
+ * The backend name for this setting.
+ */
+ public SettingFloat(String title) {
+ this(title, 0.0f, 0.0f, 0.1f, 1.0f);
+ }
+
+ /**
+ * A constructor for SettingFloat. Defaults settings to range of 0.0-1.0 and
+ * a step of 0.1.
+ *
+ * @param title
+ * The backend name for this setting.
+ * @param defValue
+ * The default value.
+ */
+ public SettingFloat(String title, float defValue) {
+ this(title, defValue, 0.0f, 0.1f, 1.0f);
+ }
+
+ /**
+ * A constructor for SettingFloat. Defaults settings to a step of 0.1.
+ *
+ * @param title
+ * The backend name for this setting.
+ * @param defValue
+ * The default value.
+ * @param minValue
+ * The minimum value.
+ * @param maxValue
+ * The maximum value.
+ */
+ public SettingFloat(String title, float defValue, float minValue,
+ float maxValue) {
+ this(title, defValue, minValue, 0.1f, maxValue);
+ }
+
+ /**
+ * A constructor for SettingFloat.
+ *
+ * @param title
+ * The backend name for this setting.
+ * @param defValue
+ * The default value.
+ * @param minValue
+ * The minimum value.
+ * @param stepValue
+ * The step value.
+ * @param maxValue
+ * The maximum value.
+ */
+ public SettingFloat(String title, float defValue, float minValue,
+ float stepValue, float maxValue) {
+ values.put("", defValue);
+ defaultValue = defValue;
+ minimumValue = minValue;
+ this.stepValue = stepValue;
+ maximumValue = maxValue;
+ backendName = title;
+ if (minimumValue > maximumValue) {
+ float t = minimumValue;
+ minimumValue = maximumValue;
+ maximumValue = t;
+ }
+ }
+
+ @Override
+ public void fromString(String s, String context) {
+ values.put(context, new Float(s));
+ if (displayWidget != null) {
+ displayWidget.update();
+ }
+ }
+
+ @Override
+ public Float get(String context) {
+ if (values.get(context) != null) {
+ return values.get(context);
+ } else if (values.get("") != null) {
+ return values.get("");
+ } else {
+ return defaultValue;
+ }
+ }
+
+ @Override
+ public void set(Float v, String context) {
+ if (stepValue > 0) {
+ values.put(context, Math.round(v / stepValue) * stepValue);
+ } else {
+ values.put(context, v);
+ }
+ if (parent != null) {
+ parent.save(context);
+ }
+ if (displayWidget != null) {
+ displayWidget.update();
+ }
+ }
+
+ @Override
+ public String toString(String context) {
+ return "" + get(context);
+ }
+}
\ No newline at end of file
diff --git a/mcp/sharose/mods/guiapi/SettingInt.java b/mcp/sharose/mods/guiapi/SettingInt.java
new file mode 100644
index 0000000..5c64a2b
--- /dev/null
+++ b/mcp/sharose/mods/guiapi/SettingInt.java
@@ -0,0 +1,133 @@
+package sharose.mods.guiapi;
+
+/**
+ * This is the Setting type for Ints.
+ *
+ * @author lahwran
+ */
+public class SettingInt extends Setting {
+ /**
+ * The maximum value.
+ */
+ public int maximumValue;
+ /**
+ * The minimum value.
+ */
+ public int minimumValue;
+ /**
+ * The step value.
+ */
+ public int stepValue;
+
+ /**
+ * A constructor for SettingInt. Defaults settings to default value of 0,
+ * range of 1-100, and a step of 1.
+ *
+ * @param title
+ * The backend name for this setting.
+ */
+ public SettingInt(String title) {
+ this(title, 0, 0, 1, 100);
+ }
+
+ /**
+ * A constructor for SettingInt. Defaults settings to range of 1-100, and a
+ * step of 1.
+ *
+ * @param title
+ * The backend name for this setting.
+ * @param defValue
+ * The default value.
+ */
+ public SettingInt(String title, int defValue) {
+ this(title, defValue, 0, 1, 100);
+ }
+
+ /**
+ * A constructor for SettingInt. Defaults settings to a step of 1.
+ *
+ * @param title
+ * The backend name for this setting.
+ * @param defValue
+ * The default value.
+ * @param minValue
+ * The minimum value.
+ * @param maxValue
+ * The maximum value.
+ */
+ public SettingInt(String title, int defValue, int minValue, int maxValue) {
+ this(title, defValue, minValue, 1, maxValue);
+ }
+
+ /**
+ * A constructor for SettingInt.
+ *
+ * @param title
+ * The backend name for this setting.
+ * @param defValue
+ * The default value.
+ * @param minValue
+ * The minimum value.
+ * @param stepValue
+ * The step value.
+ * @param maxValue
+ * The maximum value.
+ */
+ public SettingInt(String title, int defValue, int minValue, int stepValue,
+ int maxValue) {
+ values.put("", defValue);
+ defaultValue = defValue;
+ minimumValue = minValue;
+ this.stepValue = stepValue;
+ maximumValue = maxValue;
+ backendName = title;
+ if (minimumValue > maximumValue) {
+ int t = minimumValue;
+ minimumValue = maximumValue;
+ maximumValue = t;
+ }
+ }
+
+ @Override
+ public void fromString(String s, String context) {
+ values.put(context, new Integer(s));
+ if (displayWidget != null) {
+ displayWidget.update();
+ }
+ ModSettings.dbgout("fromstring " + s);
+ }
+
+ @Override
+ public Integer get(String context) {
+ if (values.get(context) != null) {
+ return values.get(context);
+ } else if (values.get("") != null) {
+ return values.get("");
+ } else {
+ return defaultValue;
+ }
+ }
+
+ @Override
+ public void set(Integer v, String context) {
+ ModSettings.dbgout("set " + v);
+ if (stepValue > 1) {
+ values.put(
+ context,
+ (int) (Math.round((float) v / (float) stepValue) * (float) stepValue));
+ } else {
+ values.put(context, v);
+ }
+ if (parent != null) {
+ parent.save(context);
+ }
+ if (displayWidget != null) {
+ displayWidget.update();
+ }
+ }
+
+ @Override
+ public String toString(String context) {
+ return "" + get(context);
+ }
+}
\ No newline at end of file
diff --git a/mcp/sharose/mods/guiapi/SettingKey.java b/mcp/sharose/mods/guiapi/SettingKey.java
new file mode 100644
index 0000000..20d0580
--- /dev/null
+++ b/mcp/sharose/mods/guiapi/SettingKey.java
@@ -0,0 +1,121 @@
+package sharose.mods.guiapi;
+
+import org.lwjgl.input.Keyboard;
+
+/**
+ * This is the Setting type for Keys.
+ *
+ * @author lahwran
+ */
+public class SettingKey extends Setting {
+ /**
+ * Constructor for SettingKey.
+ *
+ * @param title
+ * The backend name for this setting.
+ * @param key
+ * The key you want as default, as a int keycode.
+ */
+ public SettingKey(String title, int key) {
+ defaultValue = key;
+ values.put("", key);
+ backendName = title;
+ }
+
+ /**
+ * Constructor for SettingKey.
+ *
+ * @param title
+ * The backend name for this setting.
+ * @param key
+ * key The key you want as default, as a string.
+ */
+ public SettingKey(String title, String key) {
+ this(title, Keyboard.getKeyIndex(key));
+ }
+
+ @Override
+ public void fromString(String s, String context) {
+ if (s.equals("UNBOUND")) {
+ values.put(context, Keyboard.KEY_NONE);
+ } else {
+ values.put(context, Keyboard.getKeyIndex(s));
+ }
+ if (displayWidget != null) {
+ displayWidget.update();
+ }
+ }
+
+ @Override
+ public Integer get(String context) {
+ if (values.get(context) != null) {
+ return values.get(context);
+ } else if (values.get("") != null) {
+ return values.get("");
+ } else {
+ return defaultValue;
+ }
+ }
+
+ /**
+ * An easy helper to see if the current key is down.
+ *
+ * @return true if the key specified to this setting (In the current
+ * context) is down.
+ */
+ public boolean isKeyDown() {
+ return isKeyDown(ModSettings.currentContext);
+ }
+
+ /**
+ * An easy helper to see if the current key is down.
+ *
+ * @param context
+ * The context to get the key from.
+ * @return true if the key specified to this setting is down.
+ */
+ public boolean isKeyDown(String context) {
+ if (get(context) != -1) {
+ return Keyboard.isKeyDown(get(context));
+ }
+ return false;
+ }
+
+ @Override
+ public void set(Integer v, String context) {
+ values.put(context, v);
+ if (parent != null) {
+ parent.save(context);
+ }
+ if (displayWidget != null) {
+ displayWidget.update();
+ }
+ }
+
+ /**
+ * Sets the value for this setting to the current context.
+ *
+ * @param v
+ * The value, as a string.
+ */
+ public void set(String v) {
+ set(v, ModSettings.currentContext);
+ }
+
+ /**
+ * Sets the value for this setting to the specified context.
+ *
+ * @param v
+ * The value, as a string.
+ * @param context
+ * The context to set.
+ */
+ public void set(String v, String context) {
+ set(Keyboard.getKeyIndex(v), context);
+ }
+
+ @Override
+ public String toString(String context) {
+ return Keyboard.getKeyName(get(context));
+ }
+}
\ No newline at end of file
diff --git a/mcp/sharose/mods/guiapi/SettingList.java b/mcp/sharose/mods/guiapi/SettingList.java
new file mode 100644
index 0000000..9892063
--- /dev/null
+++ b/mcp/sharose/mods/guiapi/SettingList.java
@@ -0,0 +1,118 @@
+package sharose.mods.guiapi;
+
+import java.io.ByteArrayOutputStream;
+import java.util.ArrayList;
+import java.util.Iterator;
+
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.transform.Transformer;
+import javax.xml.transform.TransformerFactory;
+import javax.xml.transform.dom.DOMSource;
+import javax.xml.transform.stream.StreamResult;
+
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.NodeList;
+
+public class SettingList extends Setting> {
+
+ public SettingList(String title) {
+ this(title, new ArrayList());
+ }
+
+ public SettingList(String title, ArrayList defaultvalue) {
+ backendName = title;
+ defaultValue = defaultvalue;
+ values.put("", defaultvalue);
+ }
+
+ @Override
+ public void fromString(String s, String context) {
+ ArrayList list = new ArrayList();
+ try {
+
+ DocumentBuilderFactory builderFact = DocumentBuilderFactory
+ .newInstance();
+ builderFact.setIgnoringElementContentWhitespace(true);
+ builderFact.setValidating(true);
+ builderFact.setCoalescing(true);
+ builderFact.setIgnoringComments(true);
+ DocumentBuilder docBuilder = builderFact.newDocumentBuilder();
+ Document doc = docBuilder.parse(s);
+
+ Element localElement = (Element) doc.getChildNodes().item(1);
+
+ NodeList localNodeList = localElement.getChildNodes();
+
+ for (int i = 0; i < localNodeList.getLength(); i++) {
+ String val = localNodeList.item(i).getNodeValue();
+ list.add(val);
+ }
+ values.put(context, list);
+ if (displayWidget != null) {
+ displayWidget.update();
+ }
+ } catch (Throwable e) {
+
+ }
+ }
+
+ @Override
+ public ArrayList get(String context) {
+ if (values.get(context) != null) {
+ return values.get(context);
+ } else if (values.get("") != null) {
+ return values.get("");
+ } else {
+ return defaultValue;
+ }
+ }
+
+ @Override
+ public void set(ArrayList v, String context) {
+ values.put(context, v);
+ if (parent != null) {
+ parent.save(context);
+ }
+ if (displayWidget != null) {
+ displayWidget.update();
+ }
+ }
+
+ @Override
+ public String toString(String context) {
+ try {
+ DocumentBuilder docBuilder = DocumentBuilderFactory.newInstance()
+ .newDocumentBuilder();
+ Document doc = docBuilder.newDocument();
+ Element baseElement = (Element) doc.appendChild(doc
+ .createElement("list"));
+ ArrayList prop = get(context);
+ synchronized (prop) {
+ Iterator localIterator = prop.iterator();
+ while (localIterator.hasNext()) {
+ String str = localIterator.next();
+ baseElement.appendChild(doc.createTextNode(str));
+ }
+ }
+
+ TransformerFactory localTransformerFactory = TransformerFactory
+ .newInstance();
+ Transformer localTransformer = null;
+ localTransformer = localTransformerFactory.newTransformer();
+ localTransformer.setOutputProperty("method", "xml");
+ localTransformer.setOutputProperty("encoding", "UTF8");
+ DOMSource localDOMSource = new DOMSource(doc);
+ ByteArrayOutputStream output = new ByteArrayOutputStream();
+ StreamResult localStreamResult = new StreamResult(output);
+ localTransformer.transform(localDOMSource, localStreamResult);
+
+ return output.toString("UTF-8");
+ } catch (Throwable e) {
+ ModSettings.dbgout("Error writing SettingList from context '"
+ + context + "': " + e);
+ return "";
+ }
+ }
+}
diff --git a/mcp/sharose/mods/guiapi/SettingMulti.java b/mcp/sharose/mods/guiapi/SettingMulti.java
new file mode 100644
index 0000000..699f310
--- /dev/null
+++ b/mcp/sharose/mods/guiapi/SettingMulti.java
@@ -0,0 +1,165 @@
+package sharose.mods.guiapi;
+
+/**
+ * This is the Setting type for Multis.
+ *
+ * @author lahwran
+ */
+public class SettingMulti extends Setting {
+ /**
+ * A string array of labels for the button.
+ */
+ public String[] labelValues;
+
+ /**
+ * A constructor for SettingMulti.
+ *
+ * @param title
+ * The backend name for this setting.
+ * @param defValue
+ * The default value for this Multi.
+ * @param labelValues
+ * The text labels you would like this multi to have. Must have
+ * at least one.
+ */
+ public SettingMulti(String title, int defValue, String... labelValues) {
+ if (labelValues.length == 0) {
+ return;
+ }
+ values.put("", defValue);
+ defaultValue = defValue;
+ this.labelValues = labelValues;
+ backendName = title;
+ }
+
+ /**
+ * A constructor for SettingMulti. Default value is 0, or the first label to
+ * be defined.
+ *
+ * @param title
+ * The backend name for this setting.
+ * @param labelValues
+ * The text labels you would like this multi to have. Must have
+ * at least one.
+ */
+ public SettingMulti(String title, String... labelValues) {
+ this(title, 0, labelValues);
+ }
+
+ @Override
+ public void fromString(String s, String context) {
+ int x = -1;
+ for (int i = 0; i < labelValues.length; i++) {
+ if (labelValues[i].equals(s)) {
+ x = i;
+ }
+ }
+ if (x != -1) {
+ values.put(context, x);
+ } else {
+ values.put(context, new Float(s).intValue());
+ }
+ ModSettings.dbgout("fromstring multi " + s);
+ if (displayWidget != null) {
+ displayWidget.update();
+ }
+ }
+
+ @Override
+ public Integer get(String context) {
+ if (values.get(context) != null) {
+ return values.get(context);
+ } else if (values.get("") != null) {
+ return values.get("");
+ } else {
+ return defaultValue;
+ }
+ }
+
+ /**
+ * Helper to get the text label for the current context and value.
+ *
+ * @return The label.
+ */
+ public String getLabel() {
+ return labelValues[get()];
+ }
+
+ /**
+ * Helper to get the text label for the specified context and value.
+ *
+ * @param context
+ * The context to get the value from.
+ * @return The label.
+ */
+ public String getLabel(String context) {
+ return labelValues[get(context)];
+ }
+
+ /**
+ * Shifts the value forward for the current context.
+ */
+ public void next() {
+ next(ModSettings.currentContext);
+ }
+
+ /**
+ * Shifts the value forward for the specified context.
+ *
+ * @param context
+ * The context to change.
+ */
+ public void next(String context) {
+ int tempvalue = get(context) + 1;
+ while (tempvalue >= labelValues.length) {
+ tempvalue -= labelValues.length;
+ }
+ set(tempvalue, context);
+ }
+
+ @Override
+ public void set(Integer v, String context) {
+ values.put(context, v);
+ if (parent != null) {
+ parent.save(context);
+ }
+ if (displayWidget != null) {
+ displayWidget.update();
+ }
+ }
+
+ /**
+ * Sets the value for this setting to the current context.
+ *
+ * @param v
+ * The value, in the label representation.
+ */
+ public void set(String v) {
+ set(v, ModSettings.currentContext);
+ }
+
+ /**
+ * Sets the value for this setting to the specified context.
+ *
+ * @param v
+ * The value, in the label representation.
+ * @param context
+ * The context to set.
+ */
+ public void set(String v, String context) {
+ int x = -1;
+ for (int i = 0; i < labelValues.length; i++) {
+ if (labelValues[i].equals(v)) {
+ x = i;
+ }
+ }
+ if (x != -1) {
+ set(x, context);
+ }
+ }
+
+ @Override
+ public String toString(String context) {
+ return labelValues[get(context)];
+ }
+}
\ No newline at end of file
diff --git a/mcp/sharose/mods/guiapi/SettingText.java b/mcp/sharose/mods/guiapi/SettingText.java
new file mode 100644
index 0000000..5d31447
--- /dev/null
+++ b/mcp/sharose/mods/guiapi/SettingText.java
@@ -0,0 +1,57 @@
+package sharose.mods.guiapi;
+
+/**
+ * This is the Setting type for Text.
+ *
+ * @author lahwran
+ */
+public class SettingText extends Setting {
+ /**
+ * A constructor for SettingText.
+ *
+ * @param title
+ * The backend name for this setting.
+ * @param defaulttext
+ * The default text for this Setting.
+ */
+ public SettingText(String title, String defaulttext) {
+ values.put("", defaulttext);
+ defaultValue = defaulttext;
+ backendName = title;
+ }
+
+ @Override
+ public void fromString(String s, String context) {
+ values.put(context, s);
+ if (displayWidget != null) {
+ displayWidget.update();
+ }
+ }
+
+ @Override
+ public String get(String context) {
+ if (values.get(context) != null) {
+ return values.get(context);
+ } else if (values.get("") != null) {
+ return values.get("");
+ } else {
+ return defaultValue;
+ }
+ }
+
+ @Override
+ public void set(String v, String context) {
+ values.put(context, v);
+ if (parent != null) {
+ parent.save(context);
+ }
+ if (displayWidget != null) {
+ displayWidget.update();
+ }
+ }
+
+ @Override
+ public String toString(String context) {
+ return get(context);
+ }
+}
\ No newline at end of file
diff --git a/mcp/sharose/mods/guiapi/WidgetBoolean.java b/mcp/sharose/mods/guiapi/WidgetBoolean.java
new file mode 100644
index 0000000..f79adb3
--- /dev/null
+++ b/mcp/sharose/mods/guiapi/WidgetBoolean.java
@@ -0,0 +1,118 @@
+package sharose.mods.guiapi;
+
+import de.matthiasmann.twl.Button;
+import de.matthiasmann.twl.model.SimpleButtonModel;
+
+/**
+ * This is the Widget for boolean settings. It uses a button to display to the
+ * user.
+ *
+ * @author lahwran
+ */
+public class WidgetBoolean extends WidgetSetting implements Runnable {
+ /**
+ * The reference to the underlying Button.
+ */
+ public Button button;
+ /**
+ * The text to display on the button when the setting is false.
+ */
+ public String falseText;
+ /**
+ * The reference to the SettingBoolean that this WidgetBoolean uses.
+ */
+ public SettingBoolean settingReference = null;
+ /**
+ * The text to display on the button when the setting is true.
+ */
+ public String trueText;
+
+ /**
+ * This creates a new WidgetBoolean using the SettingBoolean and String
+ * provided. It uses 'true' and 'false' for the text.
+ *
+ * @param setting
+ * The backing setting.
+ * @param title
+ * The title for this Widget. It is what will show on the button,
+ * asides from it's current value.
+ */
+ public WidgetBoolean(SettingBoolean setting, String title) {
+ this(setting, title, "true", "false");
+ }
+
+ /**
+ * This creates a new WidgetBoolean using the WidgetBoolean and String
+ * provided, as well as setting the true and false text.
+ *
+ * @param setting
+ * The backing setting.
+ * @param title
+ * The title for this Widget. It is what will show on the button,
+ * asides from it's current value.
+ * @param truetext
+ * The text to display what the setting is true.
+ * @param falsetext
+ * The text to display what the setting is false.
+ */
+ public WidgetBoolean(SettingBoolean setting, String title, String truetext,
+ String falsetext) {
+ super(title);
+ setTheme("");
+ trueText = truetext;
+ falseText = falsetext;
+ SimpleButtonModel bmodel = new SimpleButtonModel();
+ button = new Button(bmodel);
+ bmodel.addActionCallback(this);
+ add(button);
+ settingReference = setting;
+ settingReference.displayWidget = this;
+ update();
+ }
+
+ @Override
+ public void addCallback(Runnable paramRunnable) {
+ button.getModel().addActionCallback(paramRunnable);
+ }
+
+ @Override
+ public void removeCallback(Runnable paramRunnable) {
+ button.getModel().removeActionCallback(paramRunnable);
+ }
+
+ @Override
+ public void run() {
+ if (settingReference != null) {
+ settingReference.set(
+ !settingReference.get(ModSettingScreen.guiContext),
+ ModSettingScreen.guiContext);
+ }
+ update();
+ GuiModScreen.clicksound();
+ }
+
+ @Override
+ public void update() {
+ button.setText(userString());
+ }
+
+ @Override
+ public String userString() {
+ if (settingReference != null) {
+ if (niceName.length() > 0) {
+ return String.format("%s: %s", niceName, settingReference
+ .get(ModSettingScreen.guiContext) ? trueText
+ : falseText);
+ } else {
+ return settingReference.get(ModSettingScreen.guiContext) ? trueText
+ : falseText;
+ }
+ } else {
+ if (niceName.length() > 0) {
+ return String.format("%s: %s", niceName, "no value");
+ } else {
+ return "no value or title";
+ }
+ }
+ }
+}
diff --git a/mcp/sharose/mods/guiapi/WidgetClassicTwocolumn.java b/mcp/sharose/mods/guiapi/WidgetClassicTwocolumn.java
new file mode 100644
index 0000000..ba20c49
--- /dev/null
+++ b/mcp/sharose/mods/guiapi/WidgetClassicTwocolumn.java
@@ -0,0 +1,267 @@
+package sharose.mods.guiapi;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import de.matthiasmann.twl.GUI;
+import de.matthiasmann.twl.ScrollPane;
+import de.matthiasmann.twl.Widget;
+
+/**
+ * This widget is designed to arrange widgets into two columns. The width and
+ * height is enforced, but they can be configured by the programmer.
+ *
+ * @author lahwran
+ * @author ShaRose
+ */
+public class WidgetClassicTwocolumn extends Widget {
+ /**
+ * This is the default height to enforce for widgets.
+ */
+ public int childDefaultHeight = 20;
+ /**
+ * This is the default width to enforce for widgets.
+ */
+ public int childDefaultWidth = 150;
+ /**
+ * This is the amount of padding to use between widgets vertically.
+ */
+ public int defaultPadding = 4;
+
+ protected void paintChildren(GUI gui) {
+
+ ScrollPane pane = ScrollPane.getContainingScrollPane(this);
+ boolean isScrolling = pane != null;
+
+ int minY = 0;
+ int maxY = 0;
+ if(isScrolling)
+ {
+ minY = getParent().getY();
+ maxY = minY + pane.getContentAreaHeight();
+ }
+
+ for(int i=0,n=getNumChildren() ; i= minY && child.getY() <= maxY)
+ {
+ draw = true;
+ }
+ }
+ }
+ if(draw)
+ {
+ paintChild(gui, child);
+ }
+ }
+ }
+ }
+
+ /**
+ * This is a map to override the heights of specific widgets. It is an
+ * override to overrideHeight. If you set the Integer as 0, it will use what
+ * the widget wants as it's height. If it is set negative, it will keep the
+ * positive part as the minimum size, but if the widget wants to grow it
+ * can. If you set anything else, it will use that height. Note that with
+ * TwoColumn widgets it will try and keep the height the same between two
+ * widgets opposite each other, so the one with the biggest height will
+ * override the other.
+ */
+ public Map heightOverrideExceptions = new HashMap();
+ /**
+ * This says whether it should override the height for all widgets.
+ */
+ public boolean overrideHeight = true;
+ /**
+ * This is the amount of room between the two columns.
+ */
+ public int splitDistance = 10;
+ /**
+ * This is the amount of padding to have before any widgets are positioned.
+ */
+ public int verticalPadding = 0;
+ /**
+ * This is a map to override the width of specific widgets. It is an
+ * override to childWidth. If you set the Integer as 0, it will use what the
+ * widget wants as it's width. If it is set negative, it will keep the
+ * positive part as the minimum size, but if the widget wants to grow it
+ * can. If you set anything else, it will use that width. Note that with
+ * TwoColumn widgets it will try and keep the width the same between two
+ * widgets opposite each other, so the one with the biggest width will
+ * override the other.
+ */
+ public Map widthOverrideExceptions = new HashMap();
+
+ /**
+ * This creates the WidgetClassicTwocolumn and adds the requested widgets.
+ *
+ * @param widgets
+ */
+ public WidgetClassicTwocolumn(Widget... widgets) {
+ for (int i = 0; i < widgets.length; ++i) {
+ add(widgets[i]);
+ }
+ setTheme("");
+ }
+
+ @Override
+ public int getPreferredHeight() {
+ int totalheight = verticalPadding;
+ for (int i = 0; i < getNumChildren(); i += 2) {
+ Widget w = getChild(i);
+ Widget w2 = null;
+ if ((i + 1) != getNumChildren()) {
+ w2 = getChild(i + 1);
+ }
+ int height = childDefaultHeight;
+ if (!overrideHeight) {
+ height = w.getPreferredHeight();
+ }
+ if (heightOverrideExceptions.containsKey(w)) {
+ Integer heightSet = heightOverrideExceptions.get(w);
+ if (heightSet < 1) {
+ height = w.getPreferredHeight();
+ heightSet = -heightSet;
+ if ((heightSet != 0) && (heightSet > height)) {
+ height = heightSet;
+ }
+ } else {
+ height = heightSet;
+ }
+ }
+ if (w2 != null) {
+ int temp = height;
+ if (!overrideHeight) {
+ temp = w2.getPreferredHeight();
+ }
+ if (heightOverrideExceptions.containsKey(w2)) {
+ Integer heightSet = heightOverrideExceptions.get(w2);
+ if (heightSet < 1) {
+ height = w.getPreferredHeight();
+ heightSet = -heightSet;
+ if ((heightSet != 0) && (heightSet > height)) {
+ height = heightSet;
+ }
+ } else {
+ height = heightSet;
+ }
+ }
+ if (temp > height) {
+ height = temp;
+ }
+ }
+ totalheight += height + defaultPadding;
+ }
+ return totalheight;
+ }
+
+ @Override
+ public int getPreferredWidth() {
+ return getParent().getWidth();
+ }
+
+ @Override
+ public void layout() {
+ if (getParent().getTheme().equals("scrollpane-notch")) {
+ verticalPadding = 10;
+ }
+ int totalheight = verticalPadding;
+ for (int i = 0; i < getNumChildren(); i += 2) {
+ Widget w = getChild(i);
+ Widget w2 = null;
+ try {
+ w2 = getChild(i + 1);
+ } catch (IndexOutOfBoundsException e) {
+ // do nothing, just means it's uneven.
+ }
+ int height = childDefaultHeight;
+ int width = childDefaultWidth;
+ if (!overrideHeight) {
+ height = w.getPreferredHeight();
+ }
+ if (heightOverrideExceptions.containsKey(w)) {
+ Integer heightSet = heightOverrideExceptions.get(w);
+
+ if (heightSet < 1) {
+ height = w.getPreferredHeight();
+ heightSet = -heightSet;
+ if ((heightSet != 0) && (heightSet > height)) {
+ height = heightSet;
+ }
+ } else {
+ height = heightSet;
+ }
+ }
+ if (widthOverrideExceptions.containsKey(w)) {
+ Integer widthSet = widthOverrideExceptions.get(w);
+
+ if (widthSet < 1) {
+ width = w.getPreferredWidth();
+ widthSet = -widthSet;
+ if ((widthSet != 0) && (widthSet > width)) {
+ width = widthSet;
+ }
+ } else {
+ width = widthSet;
+ }
+ }
+ if (w2 != null) {
+ int temph = height;
+ int tempw = width;
+ if (!overrideHeight) {
+ temph = w2.getPreferredHeight();
+ }
+ if (heightOverrideExceptions.containsKey(w2)) {
+ Integer heightSet = heightOverrideExceptions.get(w2);
+ if (heightSet < 1) {
+ height = w.getPreferredHeight();
+ heightSet = -heightSet;
+ if ((heightSet != 0) && (heightSet > height)) {
+ height = heightSet;
+ }
+ } else {
+ height = heightSet;
+ }
+ }
+ if (widthOverrideExceptions.containsKey(w2)) {
+ Integer widthSet = widthOverrideExceptions.get(w2);
+
+ if (widthSet < 1) {
+ width = w2.getPreferredWidth();
+ widthSet = -widthSet;
+ if ((widthSet != 0) && (widthSet > width)) {
+ width = widthSet;
+ }
+ } else {
+ width = widthSet;
+ }
+ }
+ if (temph > height) {
+ height = temph;
+ }
+ if (tempw > width) {
+ width = tempw;
+ }
+ }
+ w.setSize(width, height);
+ w.setPosition((getX() + (getWidth() / 2))
+ - (width + (splitDistance / 2)), getY() + totalheight);
+ if (w2 != null) {
+ w2.setSize(width, height);
+ w2.setPosition(getX() + (getWidth() / 2) + (splitDistance / 2),
+ getY() + totalheight);
+ }
+ totalheight += height + defaultPadding;
+ }
+ }
+}
diff --git a/mcp/sharose/mods/guiapi/WidgetFloat.java b/mcp/sharose/mods/guiapi/WidgetFloat.java
new file mode 100644
index 0000000..a58bb88
--- /dev/null
+++ b/mcp/sharose/mods/guiapi/WidgetFloat.java
@@ -0,0 +1,94 @@
+package sharose.mods.guiapi;
+
+import de.matthiasmann.twl.model.SimpleFloatModel;
+
+/**
+ * This is the Widget for Float settings. It uses a WidgetSlider to display to
+ * the user.
+ *
+ * @author lahwran
+ */
+public class WidgetFloat extends WidgetSetting implements Runnable {
+ /**
+ * The number of decimal places to display to the user.
+ */
+ public int decimalPlaces;
+ /**
+ * The reference to the SettingInt that this WidgetInt uses.
+ */
+ public SettingFloat settingReference;
+ /**
+ * The reference to the underlying WidgetSlider.
+ */
+ public WidgetSlider slider;
+
+ /**
+ * This creates a new WidgetInt using the SettingInt and String provided. It
+ * defaults the decimal places to display at 2.
+ *
+ * @param setting
+ * The backing setting.
+ * @param title
+ * The text that will show on the WidgetSlider.
+ */
+ public WidgetFloat(SettingFloat setting, String title) {
+ this(setting, title, 2);
+ }
+
+ /**
+ * This creates a new WidgetInt using the SettingInt and String provided, as
+ * well as setting how many decimal places to use.
+ *
+ * @param setting
+ * The backing setting.
+ * @param title
+ * The text that will show on the WidgetSlider.
+ */
+ public WidgetFloat(SettingFloat setting, String title, int _decimalPlaces) {
+ super(title);
+ setTheme("");
+ decimalPlaces = _decimalPlaces;
+ settingReference = setting;
+ settingReference.displayWidget = this;
+ SimpleFloatModel smodel = new SimpleFloatModel(
+ settingReference.minimumValue, settingReference.maximumValue,
+ settingReference.get());
+ smodel.addCallback(this);
+ slider = new WidgetSlider(smodel);
+ if ((settingReference.stepValue > 0.0f)
+ && (settingReference.stepValue <= settingReference.maximumValue)) {
+ slider.setStepSize(settingReference.stepValue);
+ }
+ slider.setFormat(String.format("%s: %%.%df", niceName, decimalPlaces));
+ add(slider);
+ update();
+ }
+
+ @Override
+ public void addCallback(Runnable paramRunnable) {
+ slider.getModel().addCallback(paramRunnable);
+ }
+
+ @Override
+ public void removeCallback(Runnable paramRunnable) {
+ slider.getModel().removeCallback(paramRunnable);
+ }
+
+ @Override
+ public void run() {
+ settingReference.set(slider.getValue(), ModSettingScreen.guiContext);
+ }
+
+ @Override
+ public void update() {
+ slider.setValue(settingReference.get(ModSettingScreen.guiContext));
+ slider.setMinMaxValue(settingReference.minimumValue, settingReference.maximumValue);
+ slider.setFormat(String.format("%s: %%.%df", niceName, decimalPlaces));
+ }
+
+ @Override
+ public String userString() {
+ String l = String.format("%02d", decimalPlaces);
+ return String.format("%s: %." + l + "f", niceName, settingReference);
+ }
+}
diff --git a/mcp/sharose/mods/guiapi/WidgetInt.java b/mcp/sharose/mods/guiapi/WidgetInt.java
new file mode 100644
index 0000000..3ecb282
--- /dev/null
+++ b/mcp/sharose/mods/guiapi/WidgetInt.java
@@ -0,0 +1,80 @@
+package sharose.mods.guiapi;
+
+import de.matthiasmann.twl.model.SimpleFloatModel;
+
+/**
+ * This is the Widget for Integer settings. It uses a WidgetSlider to display to
+ * the user.
+ *
+ * @author lahwran
+ */
+public class WidgetInt extends WidgetSetting implements Runnable {
+ /**
+ * The reference to the SettingInt that this WidgetInt uses.
+ */
+ public SettingInt settingReference;
+ /**
+ * The reference to the underlying WidgetSlider.
+ */
+ public WidgetSlider slider;
+
+ /**
+ * This creates a new WidgetInt using the SettingInt and String provided.
+ *
+ * @param setting
+ * The backing setting.
+ * @param title
+ * The text that will show on the WidgetSlider.
+ */
+ public WidgetInt(SettingInt setting, String title) {
+ super(title);
+ setTheme("");
+ settingReference = setting;
+ settingReference.displayWidget = this;
+ SimpleFloatModel smodel = new SimpleFloatModel(
+ settingReference.minimumValue, settingReference.maximumValue,
+ settingReference.get());
+ slider = new WidgetSlider(smodel);
+ slider.setFormat(String.format("%s: %%.0f", niceName));
+ if ((settingReference.stepValue > 1)
+ && (settingReference.stepValue <= settingReference.maximumValue)) {
+ slider.setStepSize(settingReference.stepValue);
+ }
+ smodel.addCallback(this);
+ add(slider);
+ update();
+ }
+
+ @Override
+ public void addCallback(Runnable paramRunnable) {
+ slider.getModel().addCallback(paramRunnable);
+ }
+
+ @Override
+ public void removeCallback(Runnable paramRunnable) {
+ slider.getModel().removeCallback(paramRunnable);
+ }
+
+ @Override
+ public void run() {
+ ModSettings.dbgout("run " + (int) slider.getValue());
+ settingReference.set((int) slider.getValue(),
+ ModSettingScreen.guiContext);
+ }
+
+ @Override
+ public void update() {
+ slider.setValue(settingReference.get(ModSettingScreen.guiContext));
+ slider.setMinMaxValue(settingReference.minimumValue, settingReference.maximumValue);
+ slider.setFormat(String.format("%s: %%.0f", niceName));
+ ModSettings.dbgout("update "
+ + settingReference.get(ModSettingScreen.guiContext) + " -> "
+ + (int) slider.getValue());
+ }
+
+ @Override
+ public String userString() {
+ return String.format("%s: %.0d", niceName,
+ settingReference.get(ModSettingScreen.guiContext));
+ }
+}
\ No newline at end of file
diff --git a/mcp/sharose/mods/guiapi/WidgetItem2DRender.java b/mcp/sharose/mods/guiapi/WidgetItem2DRender.java
new file mode 100644
index 0000000..b2d76de
--- /dev/null
+++ b/mcp/sharose/mods/guiapi/WidgetItem2DRender.java
@@ -0,0 +1,278 @@
+package sharose.mods.guiapi;
+
+import java.lang.reflect.Field;
+
+import net.minecraft.client.Minecraft;
+import net.minecraft.client.renderer.RenderHelper;
+import net.minecraft.client.renderer.Tessellator;
+import net.minecraft.client.renderer.entity.RenderItem;
+import net.minecraft.item.Item;
+import net.minecraft.item.ItemStack;
+
+import org.lwjgl.opengl.GL11;
+
+import cpw.mods.fml.common.ObfuscationReflectionHelper;
+import cpw.mods.fml.common.asm.transformers.deobf.FMLDeobfuscatingRemapper;
+
+import de.matthiasmann.twl.GUI;
+import de.matthiasmann.twl.Widget;
+
+/**
+ * This is a widget designed to render Minecraft objects. It has a MINIMUM size
+ * of 16X16 by default. It also (Again, by default) renders a background and
+ * border as per the theme for progressbars. This can be changed via setTheme,
+ * see mod_GuiApiTWLExamples.SetUpColouringWindow comments for details.
+ *
+ * @author ShaRose
+ *
+ */
+public class WidgetItem2DRender extends Widget {
+
+ private static RenderItem itemRenderer = new RenderItem();
+
+ private ItemStack renderStack;
+
+ private int scaleType = 0;
+
+ /**
+ * This sets up the Widget to render no object (Air, specifically).
+ */
+ public WidgetItem2DRender() {
+ this(0);
+ }
+
+ /**
+ * This makes the widget render the Item that is in the slot dictated by
+ * renderID. Note, if that ID slot is empty it will render as if you pass 0,
+ * and that the damage will be 0.
+ *
+ * @param renderID
+ */
+ public WidgetItem2DRender(int renderID) {
+ this(new ItemStack(renderID, 0, 0));
+ }
+
+ /**
+ * This makes the widget render the ItemStack passed.
+ *
+ * @param renderStack
+ */
+ public WidgetItem2DRender(ItemStack renderStack) {
+ setMinSize(16, 16);
+ setTheme("/progressbar");
+ setRenderStack(renderStack);
+ }
+
+ /**
+ * This gets the current ID this Widget is supposed to render.
+ *
+ * @return The current ID to render.
+ */
+ public int getRenderID() {
+ return renderStack == null ? 0 : renderStack.itemID;
+ }
+
+ /**
+ * This gets the ItemStack this Widget is supposed to render.
+ *
+ * @return The current ID to render.
+ */
+ public ItemStack getRenderStack() {
+ return renderStack;
+ }
+
+ /**
+ * This returns an integer that specifies what kind of Scale type it is.
+ *
+ * @return The scale type.
+ */
+ public int getScaleType() {
+ return scaleType;
+ }
+
+ @Override
+ protected void paintWidget(GUI gui) {
+
+ Minecraft minecraft = ModSettings.getMcinst();
+
+ int x = getX();
+ int y = getY();
+ float scalex = 1f;
+ float scaley = 1f;
+
+ int maxWidth = getInnerWidth() - 4;
+ int maxHeight = getInnerHeight() - 4;
+
+ int scale = getScaleType();
+
+ if ((scale == -1) && ((maxWidth < 16) || (maxHeight < 16))) {
+ scale = 0;
+ }
+
+ switch (scale) {
+ case 0: {
+ // largest square
+ int size = 0;
+ if (maxWidth > maxHeight) {
+ size = maxHeight;
+ } else {
+ size = maxWidth;
+ }
+
+ x += ((maxWidth - size) / 2);
+ y += ((maxHeight - size) / 2);
+
+ scalex = size / 16f;
+ scaley = scalex;
+ x /= scalex;
+ y /= scaley;
+ break;
+ }
+
+ case -1: {
+ // default size in middle
+ int size = maxWidth - 16;
+ x += size / 2;
+
+ size = maxHeight - 16;
+ y += size / 2;
+ break;
+ }
+
+ case 1: {
+ // fill / stretch
+ scalex = maxWidth / 16f;
+ scaley = maxHeight / 16f;
+ x /= scalex;
+ y /= scaley;
+ break;
+ }
+ default:
+ throw new IndexOutOfBoundsException(
+ "Scale Type is out of bounds! This should never happen!");
+ }
+
+ x += 2;
+ y += 1;
+
+ if ((minecraft == null) || (getRenderStack() == null)
+ || (getRenderStack().getItem() == null)) {
+ // draw black or something? Maybe NULL?
+ return;
+ }
+
+ GuiWidgetScreen screen = GuiWidgetScreen.getInstance();
+ screen.renderer.pauseRendering();
+
+ screen.renderer.setClipRect();
+ GL11.glEnable(GL11.GL_SCISSOR_TEST);
+ GL11.glPushMatrix();
+ GL11.glDisable(GL11.GL_BLEND);
+ GL11.glEnable(32826 /* GL_RESCALE_NORMAL_EXT *//* GL_RESCALE_NORMAL_EXT */);
+ RenderHelper.enableStandardItemLighting();
+ RenderHelper.enableGUIStandardItemLighting();
+
+ GL11.glScalef(scalex, scaley, 1);
+
+ ItemStack stack = getRenderStack();
+
+ Tessellator.instance.isDrawing = false;
+
+ int stackBeforeDraw = GL11.glGetInteger(GL11.GL_MODELVIEW_STACK_DEPTH);
+ try {
+
+ WidgetItem2DRender.itemRenderer
+ .renderItemIntoGUI(minecraft.fontRenderer,
+ minecraft.renderEngine, stack, x, y);
+ Tessellator.instance.isDrawing = false;
+ WidgetItem2DRender.itemRenderer
+ .renderItemOverlayIntoGUI(minecraft.fontRenderer,
+ minecraft.renderEngine, stack, x, y);
+ Tessellator.instance.isDrawing = false;
+ } catch (Throwable e) {
+ Tessellator.instance.isDrawing = false;
+ }
+
+ int stackAfterDraw = GL11.glGetInteger(GL11.GL_MODELVIEW_STACK_DEPTH);
+
+ if(stackBeforeDraw != stackAfterDraw)
+ {
+ //Yes, this IS stuff to work around 'bad' mods :D
+ for (int i = 0; i < (stackAfterDraw - stackBeforeDraw); i++) {
+ GL11.glPopMatrix();
+ }
+ }
+
+ RenderHelper.disableStandardItemLighting();
+ GL11.glDisable(32826 /* GL_RESCALE_NORMAL_EXT *//* GL_RESCALE_NORMAL_EXT */);
+
+ GL11.glPopMatrix();
+ GL11.glDisable(GL11.GL_SCISSOR_TEST);
+ screen.renderer.resumeRendering();
+ }
+
+ /**
+ * This sets the current ID to render. This checks bounds. ItemStack damage
+ * and count will stay the same.
+ *
+ * @param renderID
+ * The ID you want this widget to render.
+ */
+ public void setRenderID(int renderID) {
+ if ((renderID >= Item.itemsList.length) || (renderID < 0)) {
+ throw new IndexOutOfBoundsException(
+ String.format(
+ "Render ID must be within the possible bounds of an Item ID! (%s - %s)",
+ 0, Item.itemsList.length - 1));
+ }
+ if (renderStack == null) {
+ renderStack = new ItemStack(renderID, 0, 0);
+ }
+ renderStack.itemID = renderID;
+ }
+
+ /**
+ * This sets the ItemStack to render. This checks bounds.
+ *
+ * @param stack
+ * The ItemStack you want this widget to render. Can't be null.
+ */
+ public void setRenderStack(ItemStack stack) {
+ if (stack == null) {
+ throw new IllegalArgumentException("stack cannot be null.");
+ }
+ if ((stack.itemID >= Item.itemsList.length) || (stack.itemID < 0)) {
+ throw new IndexOutOfBoundsException(
+ String.format(
+ "Render ID must be within the possible bounds of an Item ID! (%s - %s)",
+ 0, Item.itemsList.length - 1));
+ }
+ renderStack = stack;
+ }
+
+ /**
+ * This sets what kind of scaling to use for this widget. Possible types
+ * are:
+ *
+ * -1: This doesn't scale it at all. It will be rendered right in the
+ * middle, at 16x16 size if possible. If it needs to be smaller, it will
+ * scale.
+ *
+ * 0: This is the default mode. It scales to the biggest square it can, at
+ * stays in the middle.
+ *
+ * 1: This scales to fill all the space, whether or not that space is
+ * square.
+ *
+ * @param scaleType
+ */
+ public void setScaleType(int scaleType) {
+ if (scaleType > 1) {
+ scaleType = 1;
+ }
+ if (scaleType < -1) {
+ scaleType = -1;
+ }
+ this.scaleType = scaleType;
+ }
+}
diff --git a/mcp/sharose/mods/guiapi/WidgetKeybinding.java b/mcp/sharose/mods/guiapi/WidgetKeybinding.java
new file mode 100644
index 0000000..f98ded2
--- /dev/null
+++ b/mcp/sharose/mods/guiapi/WidgetKeybinding.java
@@ -0,0 +1,108 @@
+package sharose.mods.guiapi;
+
+import org.lwjgl.input.Keyboard;
+
+import de.matthiasmann.twl.Event;
+import de.matthiasmann.twl.ToggleButton;
+import de.matthiasmann.twl.model.SimpleBooleanModel;
+
+/**
+ * This is the Widget for Key binding settings. It uses a ToggleButton to
+ * display to the user.
+ *
+ * @author lahwran
+ */
+public class WidgetKeybinding extends WidgetSetting implements Runnable {
+ /**
+ * The reference to the underlying SimpleBooleanModel.
+ */
+ public SimpleBooleanModel booleanModel;
+ /**
+ * The constant for clearing the existing key.
+ */
+ public int CLEARKEY = Keyboard.KEY_DELETE;
+ /**
+ * The constant for exiting and keeping the existing key.
+ */
+ public int NEVERMINDKEY = Keyboard.KEY_ESCAPE;
+ /**
+ * The reference to the SettingKey that this WidgetKeybinding uses.
+ */
+ public SettingKey settingReference;
+ /**
+ * The reference to the underlying ToggleButton.
+ */
+ public ToggleButton toggleButton;
+
+ /**
+ * This creates a new WidgetKeybinding using the WidgetKeybinding and String
+ * provided.
+ *
+ * @param setting
+ * The backing setting.
+ * @param title
+ * The text that will show on the WidgetSlider.
+ */
+ public WidgetKeybinding(SettingKey setting, String title) {
+ super(title);
+ setTheme("");
+ settingReference = setting;
+ settingReference.displayWidget = this;
+ booleanModel = new SimpleBooleanModel(false);
+ toggleButton = new ToggleButton(booleanModel);
+ add(toggleButton);
+ update();
+ }
+
+ @Override
+ public void addCallback(Runnable paramRunnable) {
+ booleanModel.addCallback(paramRunnable);
+ }
+
+ @Override
+ public boolean handleEvent(Event evt) {
+ if ((evt.isKeyEvent() && !evt.isKeyPressedEvent())
+ && booleanModel.getValue()) {
+ System.out.println(Keyboard.getKeyName(evt.getKeyCode()));
+ int tmpvalue = evt.getKeyCode();
+ if (tmpvalue == CLEARKEY) {
+ settingReference.set(Keyboard.KEY_NONE,
+ ModSettingScreen.guiContext);
+ } else if (tmpvalue != NEVERMINDKEY) {
+ settingReference.set(tmpvalue, ModSettingScreen.guiContext);
+ }
+ booleanModel.setValue(false);
+ update();
+ GuiModScreen.clicksound();
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ public void keyboardFocusLost() {
+ GuiModScreen.clicksound();
+ booleanModel.setValue(false);
+ }
+
+ @Override
+ public void removeCallback(Runnable paramRunnable) {
+ booleanModel.removeCallback(paramRunnable);
+ }
+
+ @Override
+ public void run() {
+ GuiModScreen.clicksound();
+ }
+
+ @Override
+ public void update() {
+ toggleButton.setText(userString());
+ }
+
+ @Override
+ public String userString() {
+ return String.format("%s: %s", niceName, Keyboard
+ .getKeyName(settingReference.get(ModSettingScreen.guiContext)));
+ }
+}
diff --git a/mcp/sharose/mods/guiapi/WidgetList.java b/mcp/sharose/mods/guiapi/WidgetList.java
new file mode 100644
index 0000000..c9f1b59
--- /dev/null
+++ b/mcp/sharose/mods/guiapi/WidgetList.java
@@ -0,0 +1,100 @@
+package sharose.mods.guiapi;
+
+import de.matthiasmann.twl.CallbackWithReason;
+import de.matthiasmann.twl.Label;
+import de.matthiasmann.twl.ListBox;
+import de.matthiasmann.twl.ListBox.CallbackReason;
+import de.matthiasmann.twl.model.SimpleChangableListModel;
+import de.matthiasmann.twl.utils.CallbackSupport;
+
+public class WidgetList extends WidgetSetting implements
+ CallbackWithReason {
+
+ private Runnable[] callbacks;
+ public Label displayLabel;
+
+ /**
+ * The reference to the underlying WidgetSlider.
+ */
+ public ListBox listBox;
+
+ public SimpleChangableListModel listBoxModel;
+
+ /**
+ * The reference to the SettingInt that this WidgetInt uses.
+ */
+ public SettingList settingReference;
+
+ public WidgetList(SettingList setting, String title) {
+ super(title);
+ setTheme("");
+ settingReference = setting;
+ settingReference.displayWidget = this;
+ if (title != null) {
+ displayLabel = new Label();
+ displayLabel.setText(niceName);
+ add(displayLabel);
+ }
+
+ listBoxModel = new SimpleChangableListModel(setting.get());
+ listBox = new ListBox(listBoxModel);
+ add(listBox);
+ listBox.addCallback(this);
+ update();
+ }
+
+ @Override
+ public void addCallback(Runnable callback) {
+ callbacks = (CallbackSupport.addCallbackToList(callbacks, callback,
+ Runnable.class));
+ }
+
+ @Override
+ public void callback(CallbackReason paramT) {
+ CallbackSupport.fireCallbacks(callbacks);
+ }
+
+ @Override
+ public void layout() {
+ if (displayLabel != null) {
+ displayLabel.setPosition(getX(), getY());
+ int offset = displayLabel.computeTextHeight();
+ displayLabel.setSize(getWidth(), offset);
+ listBox.setPosition(getX(), getY() + offset);
+ listBox.setSize(getWidth(), getHeight() - offset);
+ } else {
+ listBox.setPosition(getX(), getY());
+ listBox.setSize(getWidth(), getHeight());
+ }
+ }
+
+ @Override
+ public void removeCallback(Runnable callback) {
+ callbacks = (CallbackSupport
+ .removeCallbackFromList(callbacks, callback));
+ }
+
+ @Override
+ public void update() {
+ listBoxModel.clear();
+ listBoxModel.addElements(settingReference.get());
+ }
+
+ @Override
+ public String userString() {
+ String output = "";
+ if (niceName != null) {
+ output = niceName + ": ";
+ }
+
+ int sel = listBox.getSelected();
+ String text = sel != -1 ? listBoxModel.getEntry(sel) : "NOTHING";
+
+ output += String.format(
+ "%s (Entry %s) currently selected from %s items.", text, sel,
+ settingReference.get().size());
+
+ return output;
+ }
+
+}
diff --git a/mcp/sharose/mods/guiapi/WidgetMulti.java b/mcp/sharose/mods/guiapi/WidgetMulti.java
new file mode 100644
index 0000000..0537264
--- /dev/null
+++ b/mcp/sharose/mods/guiapi/WidgetMulti.java
@@ -0,0 +1,76 @@
+package sharose.mods.guiapi;
+
+import de.matthiasmann.twl.Button;
+import de.matthiasmann.twl.model.SimpleButtonModel;
+
+/**
+ * This is the Widget for Multi settings. It uses a button to display to the
+ * user, and cycles the values.
+ *
+ * @author lahwran
+ */
+public class WidgetMulti extends WidgetSetting implements Runnable {
+ /**
+ * The reference to the underlying Button.
+ */
+ public Button button;
+ /**
+ * The reference to the SettingMulti that this WidgetMulti uses.
+ */
+ public SettingMulti value;
+
+ /**
+ * This creates a new WidgetMulti using the SettingMulti and String
+ * provided.
+ *
+ * @param setting
+ * The backing setting.
+ * @param title
+ * The title for this Widget. It is what will show on the button,
+ * asides from it's current value.
+ */
+ public WidgetMulti(SettingMulti setting, String title) {
+ super(title);
+ setTheme("");
+ value = setting;
+ value.displayWidget = this;
+ SimpleButtonModel model = new SimpleButtonModel();
+ button = new Button(model);
+ model.addActionCallback(this);
+ add(button);
+ update();
+ }
+
+ @Override
+ public void addCallback(Runnable paramRunnable) {
+ button.getModel().addActionCallback(paramRunnable);
+ }
+
+ @Override
+ public void removeCallback(Runnable paramRunnable) {
+ button.getModel().removeActionCallback(paramRunnable);
+ }
+
+ @Override
+ public void run() {
+ value.next(ModSettingScreen.guiContext);
+ update();
+ GuiModScreen.clicksound();
+ }
+
+ @Override
+ public void update() {
+ button.setText(userString());
+ ModSettings.dbgout("multi update " + userString());
+ }
+
+ @Override
+ public String userString() {
+ if (niceName.length() > 0) {
+ return String.format("%s: %s", niceName,
+ value.getLabel(ModSettingScreen.guiContext));
+ } else {
+ return value.getLabel(ModSettingScreen.guiContext);
+ }
+ }
+}
diff --git a/mcp/sharose/mods/guiapi/WidgetSetting.java b/mcp/sharose/mods/guiapi/WidgetSetting.java
new file mode 100644
index 0000000..bb93ba8
--- /dev/null
+++ b/mcp/sharose/mods/guiapi/WidgetSetting.java
@@ -0,0 +1,94 @@
+package sharose.mods.guiapi;
+
+import java.util.ArrayList;
+
+import de.matthiasmann.twl.Widget;
+
+/**
+ * This is the base class for Widgets that are supposed to be front ends for
+ * Settings.
+ *
+ * @author lahwran
+ */
+public abstract class WidgetSetting extends Widget {
+ /**
+ * This is a list of all WidgetSetting instances.
+ */
+ public static ArrayList all = new ArrayList();
+
+ /**
+ * This updates all Widgets with the backing setting's current values.
+ */
+ public static void updateAll() {
+ for (int i = 0; i < WidgetSetting.all.size(); i++) {
+ WidgetSetting.all.get(i).update();
+ }
+ }
+
+ /**
+ * The name that will be shown on the widget.
+ */
+ public String niceName;
+
+ /**
+ * This sets the Nice Name and adds itself to the list of instances. Note
+ * this class is abstract, so you will not be using this constructor.
+ *
+ * @param nicename
+ * The nice name for this WidgetSetting.
+ */
+ public WidgetSetting(String nicename) {
+ niceName = nicename;
+ WidgetSetting.all.add(this);
+ }
+
+ @Override
+ public void add(Widget child) {
+ String T = child.getTheme();
+ if (T.length() == 0) {
+ child.setTheme("/-defaults");
+ } else if (!T.substring(0, 1).equals("/")) {
+ child.setTheme("/" + T);
+ }
+ super.add(child);
+ }
+
+ /**
+ * This adds a callback to the displayed widget (Button, Slider, etc)
+ *
+ * @param paramRunnable
+ * The Runnable callback you wish to call if the value changes.
+ */
+ public abstract void addCallback(Runnable paramRunnable);
+
+ @Override
+ public void layout() {
+ for (int i = 0; i < getNumChildren(); i++) {
+ Widget w = getChild(i);
+ w.setPosition(getX(), getY());
+ w.setSize(getWidth(), getHeight());
+ }
+ }
+
+ /**
+ * This removes a callback to the displayed widget (Button, Slider, etc) if
+ * you previously set one up.
+ *
+ * @param paramRunnable
+ * The Runnable callback you wish to remove.
+ */
+ public abstract void removeCallback(Runnable paramRunnable);
+
+ /**
+ * This method updates the widget with the backing setting store.
+ */
+ public abstract void update();
+
+ /**
+ * This returns a clean string that shows the Nice Name and the current
+ * value.
+ *
+ * @return A descriptor string.
+ */
+ public abstract String userString();
+}
diff --git a/mcp/sharose/mods/guiapi/WidgetSimplewindow.java b/mcp/sharose/mods/guiapi/WidgetSimplewindow.java
new file mode 100644
index 0000000..f12acb1
--- /dev/null
+++ b/mcp/sharose/mods/guiapi/WidgetSimplewindow.java
@@ -0,0 +1,160 @@
+package sharose.mods.guiapi;
+
+import de.matthiasmann.twl.Button;
+import de.matthiasmann.twl.Label;
+import de.matthiasmann.twl.ScrollPane;
+import de.matthiasmann.twl.Widget;
+import de.matthiasmann.twl.model.SimpleButtonModel;
+
+/**
+ * This widget is designed to make an easy base for menus. It include a
+ * ScrollPane for the main widget, a title bar on top, and a button to go back
+ * to the previous menu.
+ *
+ * @author lahwran
+ * @author ShaRose
+ */
+public class WidgetSimplewindow extends Widget {
+ /**
+ * This is a reference to the back button, if created.
+ */
+ public Button backButton = new Button();
+ /**
+ * This is a reference to the row at the bottom that contains the back
+ * button.
+ */
+ public WidgetSingleRow buttonBar = new WidgetSingleRow(0, 0);
+ /**
+ * This is the padding to use on each side of the main widget.
+ */
+ public int hPadding = 30;
+ /**
+ * This is a reference to the main widget in the center.
+ */
+ public Widget mainWidget = new Widget();
+
+ /**
+ * This is a reference to the ScrollPane that the Main Widget is in.
+ */
+ public Widget scrollPane = null; //TODO: Make this ScrollPane next release.
+ /**
+ * This is a reference to the Label that acts as the title on top.
+ */
+ public Label titleWidget = new Label();
+ /**
+ * This is the padding on the bottom. Generally, this is to keep room
+ * between the bottom of the main widget's ScrollPane and the button bar.
+ */
+ public int vBottomPadding = 40;
+ /**
+ * This is the padding on the top. Generally, this is to keep room between
+ * the top of the main widget's ScrollPane and the Label.
+ */
+ public int vTopPadding = 30;
+
+ /**
+ * This is a basic constructor. It sets the title as an empty string and it
+ * creates a new WidgetClassicTwocolumn for the main widget.
+ */
+ public WidgetSimplewindow() {
+ this(new WidgetClassicTwocolumn(), "", true);
+ }
+
+ /**
+ * This is a basic constructor. It uses the passed widget as the main widget
+ * and sets the title as an empty string.
+ *
+ * @param w
+ * The widget to use in the center.
+ */
+ public WidgetSimplewindow(Widget w) {
+ this(w, "", true);
+ }
+
+ /**
+ * This is the most common constructor. This keeps everything default,
+ * although you can pass null for the title and it will remove the title bar
+ * for you.
+ *
+ * @param w
+ * The widget to use in the center.
+ * @param s
+ * The title to show on top. If null, the title bar will not be
+ * created.
+ */
+ public WidgetSimplewindow(Widget w, String s) {
+ this(w, s, true);
+ }
+
+ /**
+ * This is the more advanced constructor. This can also skip the back button
+ * creation.
+ *
+ * @param w
+ * The widget to use in the center.
+ * @param s
+ * The title to show on top. If null, the title bar will not be
+ * created.
+ * @param showbackButton
+ */
+ public WidgetSimplewindow(Widget w, String s, Boolean showbackButton) {
+ ScrollPane scrollpane = new ScrollPane(w);
+ scrollpane.setFixed(ScrollPane.Fixed.HORIZONTAL);
+ this.scrollPane = scrollpane;
+ mainWidget = w;
+ setTheme("");
+ init(showbackButton, s);
+ }
+
+ /**
+ * Initializes the WidgetSimplewindow's widgets. This is used internally.
+ *
+ * @param showBack
+ * Whether or not to show the back button.
+ * @param titleText
+ * What to set the label text to. If null, it will not have a
+ * label.
+ */
+ protected void init(Boolean showBack, String titleText) {
+ if (titleText != null) {
+ titleWidget = new Label(titleText);
+ add(titleWidget);
+ } else {
+ vTopPadding = 10;
+ }
+ if (showBack) {
+ backButton = new Button(new SimpleButtonModel());
+ backButton.getModel().addActionCallback(
+ GuiApiHelper.backModAction
+ .mergeAction(GuiApiHelper.clickModAction));
+ backButton.setText("Back");
+ buttonBar = new WidgetSingleRow(200, 20, backButton);
+ add(buttonBar);
+ } else {
+ vBottomPadding = 0;
+ }
+ add(scrollPane);
+ }
+
+ @Override
+ public void layout() {
+ if (buttonBar != null) {
+ buttonBar.setSize(buttonBar.getPreferredWidth(),
+ buttonBar.getPreferredHeight());
+ buttonBar.setPosition(
+ (getWidth() / 2) - (buttonBar.getPreferredWidth() / 2),
+ getHeight() - (buttonBar.getPreferredHeight() + 4));
+ }
+ if (titleWidget != null) {
+ titleWidget
+ .setPosition(
+ (getWidth() / 2)
+ - (titleWidget.computeTextWidth() / 2), 10);
+ titleWidget.setSize(titleWidget.computeTextWidth(),
+ titleWidget.computeTextHeight());
+ }
+ scrollPane.setPosition(hPadding, vTopPadding);
+ scrollPane.setSize(getWidth() - (hPadding * 2), getHeight()
+ - (vTopPadding + vBottomPadding));
+ }
+}
diff --git a/mcp/sharose/mods/guiapi/WidgetSingleRow.java b/mcp/sharose/mods/guiapi/WidgetSingleRow.java
new file mode 100644
index 0000000..336d215
--- /dev/null
+++ b/mcp/sharose/mods/guiapi/WidgetSingleRow.java
@@ -0,0 +1,137 @@
+package sharose.mods.guiapi;
+
+import java.util.ArrayList;
+
+import de.matthiasmann.twl.Widget;
+
+/**
+ * This is a layout widget designed to arrange your widgets in a row. It
+ * specifies height and width for each widget when you add them.
+ *
+ * @author lahwran
+ */
+public class WidgetSingleRow extends Widget {
+ /**
+ * This is the default height of any new widgets.
+ */
+ public int defaultHeight = 20;
+ /**
+ * This is the default width of any new widgets.
+ */
+ public int defaultWidth = 150;
+ protected ArrayList heights = new ArrayList();
+ protected ArrayList widgets = new ArrayList();
+ protected ArrayList widths = new ArrayList();
+ /**
+ * This defines the space between child widgets.
+ */
+ public int xSpacing = 3;
+
+ /**
+ * This creates a new WidgetSingleRow, specifying the default width and
+ * height for any new widgets, as well as adding any widgets you would like
+ * to add.
+ *
+ * @param defwidth
+ * The default width to use for any new widgets.
+ * @param defheight
+ * The default height to use for any new widgets.
+ * @param widgets
+ * The widgets you are adding.
+ */
+ public WidgetSingleRow(int defwidth, int defheight, Widget... widgets) {
+ setTheme("");
+ defaultWidth = defwidth;
+ defaultHeight = defheight;
+ for (int i = 0; i < widgets.length; i++) {
+ add(widgets[i]);
+ }
+ }
+
+ @Override
+ public void add(Widget widget) {
+ add(widget, defaultWidth, defaultHeight);
+ }
+
+ /**
+ * This adds a new Widget with specified width and height.
+ *
+ * @param widget
+ * The widget you are adding.
+ * @param width
+ * The width of the widget you are adding.
+ * @param height
+ * The height of the widget you are adding.
+ */
+ public void add(Widget widget, int width, int height) {
+ widgets.add(widget);
+ heights.add(height);
+ widths.add(width);
+ super.add(widget);
+ }
+
+ private int getHeight(int idx) {
+ if (heights.get(idx) >= 0) {
+ return heights.get(idx);
+ } else {
+ return widgets.get(idx).getPreferredHeight();
+ }
+ }
+
+ @Override
+ public int getPreferredHeight() {
+ int maxheights = 0;
+ for (int i = 0; i < heights.size(); i++) {
+ if (getHeight(i) > maxheights) {
+ maxheights = getHeight(i);
+ }
+ }
+ return maxheights;
+ }
+
+ @Override
+ public int getPreferredWidth() {
+ int totalwidth = (widths.size() - 1) * xSpacing;
+ totalwidth = totalwidth >= 0 ? totalwidth : 0;
+ for (int i = 0; i < widths.size(); i++) {
+ totalwidth += getWidth(i);
+ }
+ return totalwidth;
+ }
+
+ private int getWidth(int idx) {
+ if (widths.get(idx) >= 0) {
+ return widths.get(idx);
+ } else {
+ return widgets.get(idx).getPreferredWidth();
+ }
+ }
+
+ @Override
+ public void layout() {
+ int curXpos = 0;
+ for (int i = 0; i < widgets.size(); i++) {
+ Widget w = widgets.get(i);
+ w.setPosition(curXpos + getX(), getY());
+ w.setSize(getWidth(i), getHeight(i));
+ curXpos += getWidth(i) + xSpacing;
+ }
+ }
+
+ @Override
+ public Widget removeChild(int idx) {
+ widgets.remove(idx);
+ heights.remove(idx);
+ widths.remove(idx);
+ return super.removeChild(idx);
+ }
+
+ @Override
+ public boolean removeChild(Widget widget) {
+ int idx = widgets.indexOf(widget);
+ widgets.remove(idx);
+ heights.remove(idx);
+ widths.remove(idx);
+ return super.removeChild(widget);
+ }
+}
diff --git a/mcp/sharose/mods/guiapi/WidgetSinglecolumn.java b/mcp/sharose/mods/guiapi/WidgetSinglecolumn.java
new file mode 100644
index 0000000..9c5408e
--- /dev/null
+++ b/mcp/sharose/mods/guiapi/WidgetSinglecolumn.java
@@ -0,0 +1,100 @@
+package sharose.mods.guiapi;
+
+import de.matthiasmann.twl.Widget;
+
+/**
+ * This is a widget designed to arrange other widgets into a single column.
+ *
+ * @author lahwran
+ * @author ShaRose
+ */
+public class WidgetSinglecolumn extends WidgetClassicTwocolumn {
+ /**
+ * This creates the WidgetSinglecolumn with the specified Widgets. It
+ * chooses a default Width of 200.
+ *
+ * @param widgets
+ * The widgets to add.
+ */
+ public WidgetSinglecolumn(Widget... widgets) {
+ super(widgets);
+ childDefaultWidth = 200;
+ }
+
+ @Override
+ public int getPreferredHeight() {
+ int totalheight = verticalPadding;
+ for (int i = 0; i < getNumChildren(); ++i) {
+ Widget widget = getChild(i);
+ int height = childDefaultHeight;
+ if (!overrideHeight) {
+ height = widget.getPreferredHeight();
+ }
+ if (heightOverrideExceptions.containsKey(widget)) {
+ Integer heightSet = heightOverrideExceptions.get(widget);
+ if (heightSet < 1) {
+ height = widget.getPreferredHeight();
+ heightSet = -heightSet;
+ if ((heightSet != 0) && (heightSet > height)) {
+ height = heightSet;
+ }
+ } else {
+ height = heightSet;
+ }
+ }
+ totalheight += height + defaultPadding;
+ }
+ return totalheight;
+ }
+
+ @Override
+ public int getPreferredWidth() {
+ // I can't see why we do a check here and not on TwoColoumn, and I don't
+ // really want to loop widthOverrideExceptions, so let's just remove
+ // this particular check, mmkay?
+ // return Math.max(getParent().getWidth(), childWidth);
+ return getParent().getWidth();
+ }
+
+ @Override
+ public void layout() {
+ int totalheight = verticalPadding;
+ for (int i = 0; i < getNumChildren(); ++i) {
+ Widget w = getChild(i);
+ int height = childDefaultHeight;
+ int width = childDefaultWidth;
+ if (!overrideHeight) {
+ height = w.getPreferredHeight();
+ }
+ if (heightOverrideExceptions.containsKey(w)) {
+ Integer heightSet = heightOverrideExceptions.get(w);
+ if (heightSet < 1) {
+ height = w.getPreferredHeight();
+ heightSet = -heightSet;
+ if ((heightSet != 0) && (heightSet > height)) {
+ height = heightSet;
+ }
+ } else {
+ height = heightSet;
+ }
+ }
+ if (widthOverrideExceptions.containsKey(w)) {
+ Integer widthSet = widthOverrideExceptions.get(w);
+
+ if (widthSet < 1) {
+ width = w.getPreferredWidth();
+ widthSet = -widthSet;
+ if ((widthSet != 0) && (widthSet > width)) {
+ width = widthSet;
+ }
+ } else {
+ width = widthSet;
+ }
+ }
+ w.setSize(width, height);
+ w.setPosition((getX() + (getWidth() / 2)) - (width / 2), getY()
+ + totalheight);
+ totalheight += height + defaultPadding;
+ }
+ }
+}
diff --git a/mcp/sharose/mods/guiapi/WidgetSlider.java b/mcp/sharose/mods/guiapi/WidgetSlider.java
new file mode 100644
index 0000000..4449ff4
--- /dev/null
+++ b/mcp/sharose/mods/guiapi/WidgetSlider.java
@@ -0,0 +1,60 @@
+package sharose.mods.guiapi;
+
+import de.matthiasmann.twl.ValueAdjusterFloat;
+import de.matthiasmann.twl.model.FloatModel;
+
+/**
+ * This is a simple extension of ValueAdjusterFloat so that it always updates
+ * the setting. Used internally.
+ *
+ * @author lahwran
+ */
+public class WidgetSlider extends ValueAdjusterFloat {
+ /**
+ * This is the basic constructor. It just calls the ValueAdjusterFloat
+ * constructor, as well as adding an option to allow editing the value with text.
+ *
+ * @param f
+ * The FloatModel to use.
+ */
+ public WidgetSlider(FloatModel f) {
+ super(f);
+ }
+
+ private boolean canEdit = false;;
+
+ /**
+ * Specifies whether or not to allow the user to click on the Slider to enter a numberic value.
+ * @param value Whether or not to allow Editing.
+ */
+ public void setCanEdit(boolean value)
+ {
+ canEdit = value;
+ }
+
+ /**
+ * @return True if the user can edit this Slider by clicking on it: False otherwise.
+ */
+ public boolean getCanEdit()
+ {
+ return canEdit;
+ }
+
+ @Override
+ public void startEdit() {
+ if(!getCanEdit())
+ {
+ cancelEdit();
+ }
+ else
+ {
+ super.startEdit();
+ }
+ }
+
+ @Override
+ protected String onEditStart()
+ {
+ return Float.toString(getValue());
+ }
+}
diff --git a/mcp/sharose/mods/guiapi/WidgetText.java b/mcp/sharose/mods/guiapi/WidgetText.java
new file mode 100644
index 0000000..8245a13
--- /dev/null
+++ b/mcp/sharose/mods/guiapi/WidgetText.java
@@ -0,0 +1,131 @@
+package sharose.mods.guiapi;
+
+import de.matthiasmann.twl.EditField;
+import de.matthiasmann.twl.Label;
+import de.matthiasmann.twl.model.StringModel;
+import de.matthiasmann.twl.utils.CallbackSupport;
+
+/**
+ * This is the Widget for Text settings. It uses an EditField for the user to
+ * edit, and a Label for the name.
+ *
+ * @author lahwran
+ * @author ShaRose
+ */
+public class WidgetText extends WidgetSetting implements StringModel {
+ private Runnable[] callbacks;
+ /**
+ * The label that displays to the user what the nice name of this setting
+ * is.
+ */
+ public Label displayLabel;
+ /**
+ * The EditField that the user actually changes the setting with.
+ */
+ public EditField editField;
+ /**
+ * This is a control number to who and what can edit this setting. 0 means
+ * that both the SettingText and the user can edit. Below 0 means that only
+ * the user can edit: So resetting to default will not change the text the
+ * user sees. Over 0 means that the user can't edit the field, but if the
+ * SettingText updates, it will replace what the user sees.
+ */
+ public int setmode = 0;
+ /**
+ * The reference to the SettingText that this WidgetText uses.
+ */
+ public SettingText settingReference;
+
+ /**
+ * This creates a new WidgetText using the SettingText and String provided.
+ *
+ * @param setting
+ * The backing setting.
+ * @param title
+ * The text that will show on the Label. If null, it will not
+ * have a label at all.
+ */
+ public WidgetText(SettingText setting, String title) {
+ super(title);
+ setTheme("");
+ settingReference = setting;
+ settingReference.displayWidget = this;
+ editField = new EditField();
+ add(editField);
+ if (title != null) {
+ displayLabel = new Label();
+ displayLabel.setText(String.format("%s: ", niceName));
+ add(displayLabel);
+ }
+ editField.setModel(this);
+ update();
+ }
+
+ @Override
+ public void addCallback(Runnable callback) {
+ callbacks = (CallbackSupport.addCallbackToList(callbacks, callback,
+ Runnable.class));
+ }
+
+ @Override
+ public String getValue() {
+ return settingReference.get();
+ }
+
+ @Override
+ public void layout() {
+ if (displayLabel != null) {
+ displayLabel.setPosition(getX(), (getY() + (getHeight() / 2))
+ - (displayLabel.computeTextHeight() / 2));
+ displayLabel.setSize(displayLabel.computeTextWidth(),
+ displayLabel.computeTextHeight());
+ editField.setPosition(getX() + displayLabel.computeTextWidth(),
+ getY());
+ editField.setSize(getWidth() - displayLabel.computeTextWidth(),
+ getHeight());
+ } else {
+ editField.setPosition(getX(), getY());
+ editField.setSize(getWidth(), getHeight());
+ }
+ }
+
+ @Override
+ public void removeCallback(Runnable callback) {
+ callbacks = (CallbackSupport
+ .removeCallbackFromList(callbacks, callback));
+ }
+
+ @Override
+ public void setValue(String _value) {
+ GuiModScreen.clicksound();
+ ModSettings.dbgout(String.format("setvalue %s", editField.getText()));
+ if (setmode <= 0) {
+ setmode = -1;
+ settingReference.set(editField.getText(),
+ ModSettingScreen.guiContext);
+ setmode = 0;
+ }
+ CallbackSupport.fireCallbacks(callbacks);
+ }
+
+ @Override
+ public void update() {
+ ModSettings.dbgout("update");
+ if (displayLabel != null) {
+ displayLabel.setText(String.format("%s: ", niceName));
+ }
+ if (setmode >= 0) {
+ setmode = 1;
+ editField
+ .setText(settingReference.get(ModSettingScreen.guiContext));
+ setmode = 0;
+ }
+ ModSettings.dbgout(String.format("update %s", editField.getText()));
+ }
+
+ @Override
+ public String userString() {
+ return String.format("%s: %s", niceName,
+ settingReference.get(ModSettingScreen.guiContext));
+ }
+}
diff --git a/mcp/sharose/mods/guiapi/WidgetTick.java b/mcp/sharose/mods/guiapi/WidgetTick.java
new file mode 100644
index 0000000..14b12b2
--- /dev/null
+++ b/mcp/sharose/mods/guiapi/WidgetTick.java
@@ -0,0 +1,301 @@
+package sharose.mods.guiapi;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import de.matthiasmann.twl.GUI;
+import de.matthiasmann.twl.Widget;
+
+/**
+ * This is a dummy Widget that you can use to call a Runnable object every
+ * frame, or every X milliseconds. You can also use it for one-off events, such
+ * as calling a method 5 seconds after a Widget is shown.
+ *
+ * @author ShaRose
+ */
+public class WidgetTick extends Widget implements IWidgetAlwaysDraw {
+
+ /**
+ * This is a Tick that calls the passed Runnable each time a specific delay
+ * is passed.
+ *
+ * @author ShaRose
+ */
+ public class DelayTick implements iTick {
+
+ long lastTick;
+ boolean removeSelf = false;
+ Runnable tickCallback;
+ long timeToTick;
+
+ /**
+ * Creates a new DelayTick that calls each time a specific delay passes.
+ *
+ * @param callback
+ * The Runnable it will call.
+ * @param delay
+ * The delay on the tick. If 0, it will call on the first
+ * frame it is checked.
+ */
+ public DelayTick(Runnable callback, int delay) {
+ if (callback == null) {
+ throw new IllegalArgumentException("Callback cannot be null.");
+ }
+ if (delay < 0) {
+ throw new IllegalArgumentException("Delay must be 0 or higher.");
+ }
+ lastTick = 0;
+ timeToTick = delay;
+ tickCallback = callback;
+ }
+
+ @Override
+ public void checkTick() {
+ long millis = System.currentTimeMillis();
+ if ((lastTick + timeToTick) < millis) {
+ lastTick = millis;
+ tickCallback.run();
+ }
+ }
+
+ /**
+ * If this method is called, this tick will remove itself next frame.
+ */
+ public void removeSelf() {
+ removeSelf = true;
+ }
+
+ @Override
+ public boolean shouldRemove() {
+ return removeSelf;
+ }
+ }
+
+ /**
+ * This is a Tick that calls the passed Runnable each frame.
+ *
+ * @author ShaRose
+ */
+ public class FrameTick implements iTick {
+ boolean removeSelf = false;
+
+ Runnable tickCallback;
+
+ /**
+ * Creates a new FrameTick that calls each frame.
+ *
+ * @param callback
+ * The Runnable it will call.
+ */
+ public FrameTick(Runnable callback) {
+ if (callback == null) {
+ throw new IllegalArgumentException("Callback cannot be null.");
+ }
+ tickCallback = callback;
+ }
+
+ @Override
+ public void checkTick() {
+ tickCallback.run();
+ }
+
+ /**
+ * If this method is called, this tick will remove itself next frame.
+ */
+ public void removeSelf() {
+ removeSelf = true;
+ }
+
+ @Override
+ public boolean shouldRemove() {
+ return removeSelf;
+ }
+
+ @Override
+ public String toString() {
+ return "FrameTick [tickCallback=" + tickCallback + "]";
+ }
+ }
+
+ /**
+ * This is an interface that is used by GuiAPI for ticks. You can use it as
+ * well for creating custom tick patterns.
+ *
+ * @author ShaRose
+ */
+ public interface iTick {
+ /**
+ * This is called once a frame if this Tick is added to a WidgetTick,
+ * and said WidgetTick is drawn.
+ */
+ void checkTick();
+
+ /**
+ * @return Should this Tick be removed from the list?
+ */
+ boolean shouldRemove();
+ }
+
+ /**
+ * This is a Tick that calls the passed Runnable after a configurable delay.
+ * After it is called, it tries to remove itself.
+ *
+ * @author ShaRose
+ */
+ public class SingleTick implements iTick {
+ int delayBeforeRemove;
+
+ Runnable tickCallback;
+ long timeToTick;
+
+ /**
+ * Creates a new SingleTick that is only called once, with a
+ * configurable delay. After this one tick it removes itself.
+ *
+ * @param callback
+ * The Runnable it will call.
+ * @param delay
+ * The delay on the tick. If 0, it will call on the first
+ * frame it is checked.
+ */
+ public SingleTick(Runnable callback, int delay) {
+ if (callback == null) {
+ throw new IllegalArgumentException("Callback cannot be null.");
+ }
+ if (delay < 0) {
+ throw new IllegalArgumentException("Delay must be 0 or higher.");
+ }
+ timeToTick = -1;
+ delayBeforeRemove = delay;
+ tickCallback = callback;
+ }
+
+ @Override
+ public void checkTick() {
+ if (delayBeforeRemove == 0) {
+ tickCallback.run();
+ }
+ if (timeToTick == -1) {
+ timeToTick = System.currentTimeMillis() + delayBeforeRemove;
+ } else {
+ if (timeToTick < System.currentTimeMillis()) {
+ tickCallback.run();
+ }
+ }
+ }
+
+ @Override
+ public boolean shouldRemove() {
+ if (delayBeforeRemove == 0) {
+ return true;
+ }
+ return timeToTick < System.currentTimeMillis();
+ }
+
+ @Override
+ public String toString() {
+ return "SingleTick [tickCallback=" + tickCallback + "]";
+ }
+ }
+
+ protected ArrayList ticks = new ArrayList();
+
+ /**
+ * This creates a new WidgetTick. It does nothing besides setting the max
+ * size to 0,0.
+ */
+ public WidgetTick() {
+ setMaxSize(0, 0);
+ }
+
+ /**
+ * This creates and adds a new Tick that calls every frame.
+ *
+ * @param callback
+ * The callback you want this to call.
+ * @return {@link WidgetTick.FrameTick}
+ */
+ public FrameTick addCallback(Runnable callback) {
+ FrameTick tick = new FrameTick(callback);
+ ticks.add(tick);
+ return tick;
+ }
+
+ /**
+ * This creates and adds a tick that calls at a configurable delay.
+ *
+ * @param callback
+ * The callback you want this to call.
+ * @param timepertick
+ * @return {@link WidgetTick.DelayTick}
+ */
+ public DelayTick addCallback(Runnable callback, int timepertick) {
+ DelayTick tick = new DelayTick(callback, timepertick);
+ ticks.add(tick);
+ return tick;
+ }
+
+ /**
+ * This adds an {@link WidgetTick.iTick} to the internal array. This is so you can
+ * create your own custom ticks and add them if you want something more
+ * powerful.
+ *
+ * @param tick
+ * The {@link WidgetTick.iTick} to add.
+ * @return true if it was able to be added, false otherwise.
+ */
+ public boolean addCustomTick(iTick tick) {
+ return ticks.add(tick);
+ }
+
+ /**
+ * This creates and adds a tick that is called only once after a delay, and
+ * then removes itself.
+ *
+ * @param callback
+ * The callback you want this to call.
+ * @param delay
+ * The delay on the tick.
+ * @return The created {@link WidgetTick.SingleTick}
+ */
+ public SingleTick addTimedCallback(Runnable callback, int delay) {
+ SingleTick tick = new SingleTick(callback, delay);
+ ticks.add(tick);
+ return tick;
+ }
+
+ /**
+ * Gets an unmodifiable copy of the currently registered ticks.
+ *
+ * @return A unmodifiable copy of the tick list.
+ */
+ public List getTickArrayCopy() {
+ return Collections.unmodifiableList(ticks);
+ }
+
+ @Override
+ protected void paintWidget(GUI gui) {
+ iTick[] removedTicks = new iTick[ticks.size()];
+ for (int i = 0; i < ticks.size(); i++) {
+ iTick tick = ticks.get(i);
+ try {
+ tick.checkTick();
+ } catch (Exception e) {
+ e.printStackTrace();
+ throw new RuntimeException("Error when calling tick " + tick
+ + " at position " + i + " in WidgetTick.", e);
+ }
+ if (tick.shouldRemove()) {
+ removedTicks[i] = tick;
+ }
+ }
+ for (int i = 0; i < removedTicks.length; i++) {
+ iTick tick = removedTicks[i];
+ if (tick != null) {
+ ticks.remove(tick);
+ }
+ }
+
+ }
+}
diff --git a/mcp/sharose/mods/guiapi/examples/ItemGuiApiExample.java b/mcp/sharose/mods/guiapi/examples/ItemGuiApiExample.java
new file mode 100644
index 0000000..083f262
--- /dev/null
+++ b/mcp/sharose/mods/guiapi/examples/ItemGuiApiExample.java
@@ -0,0 +1,65 @@
+package sharose.mods.guiapi.examples;
+
+import cpw.mods.fml.relauncher.Side;
+import cpw.mods.fml.relauncher.SideOnly;
+import sharose.mods.guiapi.GuiApiHelper;
+import sharose.mods.guiapi.GuiModScreen;
+import net.minecraft.client.renderer.texture.IconRegister;
+import net.minecraft.entity.player.EntityPlayer;
+import net.minecraft.item.Item;
+import net.minecraft.item.ItemStack;
+import net.minecraft.world.World;
+import de.matthiasmann.twl.Widget;
+
+/**
+ * This is a small example for displaying widgets in game. When you use this
+ * item, it simply shows a text display with a back button.
+ *
+ * @author ShaRose
+ */
+public class ItemGuiApiExample extends Item {
+
+ /**
+ * Default constructor for Items.
+ *
+ * @param itemID
+ */
+ protected ItemGuiApiExample(int itemID) {
+ super(itemID);
+ }
+
+ @Override
+ public int getColorFromItemStack(ItemStack par1ItemStack, int par2) {
+ // This simply tints the sign black to differentiate it. This way I
+ // didn't have to use a sprite. (This isn't a part of GuiAPI)
+ return 986895;
+ }
+
+ @Override
+ public ItemStack onItemRightClick(ItemStack par1ItemStack, World par2World,
+ EntityPlayer par3EntityPlayer) {
+ Widget textDisplay = GuiApiHelper
+ .makeTextDisplayAndGoBack(
+ "An example message.",
+ "This is a message that should be displayed when you right click with the test item selected.",
+ "Go back to the game.", false);
+ // I'm going to be creating a simple text display widget. To keep things
+ // small, I'll be using an existing feature from GuiApiHelper:
+ // makeTextDisplayAndGoBack. It makes a window with a text bar on top, a
+ // message (that will size itself, and if needed will use scrollbars),
+ // and a button to go back to whatever was open previously. It should be
+ // easy to see what's what for this method.
+ GuiModScreen.show(textDisplay);
+ // Finally, 'show' your new widget.
+ return par1ItemStack;
+ // Return the ItemStack without any changes (we aren't using it up).
+ }
+
+ @Override
+ @SideOnly(Side.CLIENT)
+ public void updateIcons(IconRegister par1IconRegister)
+ {
+ this.iconIndex = Item.sign.getIconFromDamage(0);
+ return; // Please, please never do this. This is just a hack.
+ }
+}
diff --git a/mcp/sharose/mods/guiapi/examples/mod_GuiApiBasicExample.java b/mcp/sharose/mods/guiapi/examples/mod_GuiApiBasicExample.java
new file mode 100644
index 0000000..decc540
--- /dev/null
+++ b/mcp/sharose/mods/guiapi/examples/mod_GuiApiBasicExample.java
@@ -0,0 +1,143 @@
+package sharose.mods.guiapi.examples;
+
+import sharose.mods.guiapi.GuiApiHelper;
+import sharose.mods.guiapi.GuiModScreen;
+import sharose.mods.guiapi.ModSettingScreen;
+import sharose.mods.guiapi.ModSettings;
+import net.minecraft.src.BaseMod;
+
+/**
+ * This is the BASIC example of GuiAPI usage. We are going to create a
+ * ModSettings object, and use the 'easy' way of getting settings. Note, the
+ * easy was is slower than the intermediate way of doing things, so if you are
+ * getting your setting values several times a second you might want to read
+ * that after this tutorial. As well, this tutorial will show you some usage of
+ * the 'makeButton' and 'showTextDisplay' method in the GuiApiHelper class.
+ *
+ * @author ShaRose
+ */
+public class mod_GuiApiBasicExample extends BaseMod {
+ /** The mod screen. */
+ public ModSettingScreen myModScreen;
+ /** The settings. */
+ public ModSettings mySettings;
+
+ @Override
+ public String getVersion() {
+ return "1.1";
+ }
+
+ @Override
+ public void load() {
+ // First, create the settings class. The string in question is the
+ // 'backend' name, usually the actual mod class name.
+ mySettings = new ModSettings("mod_GuiApiBasicExample");
+ // This is the Mod screen. It will automatically register the button in
+ // the settings menu. This is two seperate steps, because you don't HAVE
+ // to have this step there, like if you only want to store the settings
+ // without a user-accessible gui.
+ // Since we are adding one with longish names, we are going to make this
+ // in single column mode.
+ myModScreen = new ModSettingScreen("GuiAPI Basic Example");
+ // If you want to leave it in two column mode, you don't need this part.
+ // Now let's add a few settings. Since we are doing it in 'easy' mode,
+ // we'll just discard the return values in this case.
+ myModScreen.setSingleColumn(true);
+ // default is 5, min is 1, max is 10
+ mySettings.addSetting(myModScreen, "Nice Name for Int A",
+ "backendIntA", 5, 1, 10);
+ // default is 0.5, min is 0.1, step is 0.01, max is 1.0.
+ mySettings.addSetting(myModScreen, "Nice Name for Float B",
+ "backendFloatB", 0.5f, 0.1f, 0.01f, 1.0f);
+ // Multi options, default at 'Option A', go between Option A-F
+ mySettings.addSetting(myModScreen, "Nice Name for Multi C",
+ "backendMultiC", 0, "Option A", "Option B", "Option C",
+ "Option D", "Option E", "Option F");
+ // boolean, default at true
+ mySettings.addSetting(myModScreen, "Nice Name for Boolean D",
+ "backendBooleanD", true);
+ // boolean, default at true, with custom names
+ mySettings.addSetting(myModScreen, "Nice Name for Boolean E",
+ "backendBooleanE", false, "Yes!", "Noo!");
+ // Text setting
+ mySettings.addSetting(myModScreen, "Nice Name for Text F",
+ "backendTextF", "This is the Default Value");
+ // This makes a button which will call the 'ShowAllTheSettings' method
+ // below. If the method in question is a static method, please use the
+ // class. For example, mod_GuiAPIBasicExample.class instead of this.
+ // Note you don't have to use this either, it can be any object, and the
+ // method does not need to be public, as this can and will call private
+ // methods as well. The boolean at the end controls whether or not to
+ // automatically add the click sound. Use false if you want it to be
+ // silent, or if you want to call it manually with GuiModScreen.back()
+ myModScreen.append(GuiApiHelper.makeButton("Display all my settings!",
+ "ShowAllTheSettings", this, true));
+ // We'll also add a button to reset all of the settings to default. You
+ // can do this on a per setting basis as well, but we'll learn that in
+ // the Intermediate example mod. As well, you can see that I am calling
+ // mySettings.resetAll() with this button, and it will click.
+ myModScreen.append(GuiApiHelper.makeButton("Default all my settings!",
+ "resetAll", mySettings, true));
+ // And finally, don't forget to load any settings you saved earlier!
+ mySettings.load();
+ }
+
+ /**
+ * This is the method that will be called if you press the
+ * "Display Settings" button. It doesn't have to be public, so you can make
+ * this private and it will work. However, since we are calling this from a
+ * button, it needs to return void.
+ */
+ public void ShowAllTheSettings() {
+ StringBuilder displayTextBuilder = new StringBuilder();
+ displayTextBuilder.append("Int A: ");
+ // this finds the setting by backend name. It makes sure that it is an
+ // int type, and gets the value of the current context (like a global
+ // context, or a per world context. By default, the context will not
+ // change, but you can specify specific ones if you want). You can
+ // specify one as an argument if you want as well.
+ displayTextBuilder.append(mySettings.getIntSettingValue("backendIntA"));
+ displayTextBuilder.append("\r\n\r\n");
+ displayTextBuilder.append("Float B: ");
+ // Now Float B.
+ displayTextBuilder.append(mySettings
+ .getFloatSettingValue("backendFloatB"));
+ displayTextBuilder.append("\r\n\r\n");
+ displayTextBuilder.append("Multi C: ");
+ // Now Multi C, as an int which you can use in your code.
+ displayTextBuilder.append(mySettings
+ .getMultiSettingValue("backendMultiC"));
+ displayTextBuilder.append("\r\n\r\n");
+ displayTextBuilder.append("Multi C (Displayed): ");
+ // Now Multi C, as the displayed string on the menu.
+ displayTextBuilder.append(mySettings
+ .getMultiSettingLabel("backendMultiC"));
+ displayTextBuilder.append("\r\n\r\n");
+ displayTextBuilder.append("Boolean D: ");
+ // Now Boolean D.
+ displayTextBuilder.append(mySettings
+ .getBooleanSettingValue("backendBooleanD"));
+ displayTextBuilder.append("\r\n\r\n");
+ displayTextBuilder.append("Boolean E: ");
+ // Now Boolean E. Note that it is not any different whether you
+ // specified display names or not.
+ displayTextBuilder.append(mySettings
+ .getBooleanSettingValue("backendBooleanE"));
+ displayTextBuilder.append("\r\n\r\n");
+ displayTextBuilder.append("Text F: ");
+ // Now String F.
+ displayTextBuilder.append(mySettings
+ .getTextSettingValue("backendTextF"));
+ // Display your menu. It will have a title bar that says 'My Current
+ // Settings', will use all the text you just created using the
+ // StringBuilder and the settings helpers in a long textbox, and at the
+ // bottom will have a button that says 'OK, Go back to the settings
+ // now.' which, when pressed, will just go back to the last menu. In
+ // this case, your settings menu. It automatically sets up a scrollbar
+ // for you as well. Of course, we also call GuiModScreen.show to display
+ // the widget that makeTextDisplayAndGoBack creates.
+ GuiModScreen.show(GuiApiHelper.makeTextDisplayAndGoBack(
+ "My Current Settings", displayTextBuilder.toString(),
+ "OK, Go back to the settings now.", false));
+ }
+}
diff --git a/mcp/sharose/mods/guiapi/examples/mod_GuiApiIngameMessageExample.java b/mcp/sharose/mods/guiapi/examples/mod_GuiApiIngameMessageExample.java
new file mode 100644
index 0000000..ab6c1da
--- /dev/null
+++ b/mcp/sharose/mods/guiapi/examples/mod_GuiApiIngameMessageExample.java
@@ -0,0 +1,38 @@
+package sharose.mods.guiapi.examples;
+
+import net.minecraft.item.Item;
+import net.minecraft.item.ItemStack;
+import net.minecraft.src.BaseMod;
+import net.minecraft.src.ModLoader;
+
+/**
+ * This is a small example for how to make a user dialog. This part is just
+ * creating the item. Please view {@link ItemGuiApiExample} for the real code.
+ *
+ * @author ShaRose
+ */
+public class mod_GuiApiIngameMessageExample extends BaseMod {
+
+ /**
+ * A reference to the Example item.
+ */
+ Item exampleItem;
+
+ @Override
+ public String getVersion() {
+ return "1.0";
+ }
+
+ @Override
+ public void load() {
+ exampleItem = new ItemGuiApiExample(28000).setUnlocalizedName(
+ "GuiApiExampleItem");
+ // Create the example item, set the Name. It will use the sign's icon itself.
+ ModLoader.addName(exampleItem, "GuiAPI Example Item");
+ // Give it a 'real' name.
+ ModLoader.addShapelessRecipe(new ItemStack(exampleItem), new Object[] {
+ new ItemStack(Item.sign), new ItemStack(Item.paper) });
+ // Just a shapeless recipe. Normal sign and some paper.
+ }
+
+}
diff --git a/mcp/sharose/mods/guiapi/examples/mod_GuiApiIntermediateExample.java b/mcp/sharose/mods/guiapi/examples/mod_GuiApiIntermediateExample.java
new file mode 100644
index 0000000..01ad73c
--- /dev/null
+++ b/mcp/sharose/mods/guiapi/examples/mod_GuiApiIntermediateExample.java
@@ -0,0 +1,263 @@
+package sharose.mods.guiapi.examples;
+
+import sharose.mods.guiapi.GuiApiHelper;
+import sharose.mods.guiapi.GuiModScreen;
+import sharose.mods.guiapi.ModAction;
+import sharose.mods.guiapi.ModSettingScreen;
+import sharose.mods.guiapi.ModSettings;
+import sharose.mods.guiapi.Setting;
+import sharose.mods.guiapi.SettingBoolean;
+import sharose.mods.guiapi.SettingFloat;
+import sharose.mods.guiapi.SettingInt;
+import sharose.mods.guiapi.SettingMulti;
+import sharose.mods.guiapi.SettingText;
+import sharose.mods.guiapi.WidgetSimplewindow;
+import sharose.mods.guiapi.WidgetSinglecolumn;
+import net.minecraft.src.BaseMod;
+import de.matthiasmann.twl.TextArea;
+import de.matthiasmann.twl.Widget;
+
+/**
+ * This is the INTERMEDIATE example of GuiAPI usage. We are going to do the more
+ * correct, but slightly more complex, way of retrieving settings, learn about
+ * Callback usage within GuiAPI, and some more interesting usages of ModAction
+ * relating to that. We will also learn about subscreens and how to use them
+ * within your mod. We'll also be looking at the createChoiceMenu method in
+ * GuiApiHelper.
+ *
+ * @author ShaRose
+ */
+public class mod_GuiApiIntermediateExample extends BaseMod {
+ /**
+ * This is a method designed to update the text area, depending on what kind
+ * of setting is passed. Please view the source code comments for more.
+ *
+ * @param textArea
+ * The textarea to update.
+ * @param setting
+ * The setting to get info from. This particular method supports
+ * SettingInt, SettingFloat, and SettingText.
+ */
+ private static void updateTextArea(TextArea textArea,
+ @SuppressWarnings("rawtypes") Setting setting) {
+ String text = "";
+ // Instead of making different method for each one, We'll just check
+ // what type of setting each is, place the text you want in the text
+ // variable, and then set the TextArea.
+ if (setting instanceof SettingInt) {
+ SettingInt settingint = (SettingInt) setting;
+ text = (((settingint.get() - settingint.minimumValue) / (float) (settingint.maximumValue - settingint.minimumValue)) * 100)
+ + "%";
+ }
+ if (setting instanceof SettingFloat) {
+ SettingFloat settingfloat = (SettingFloat) setting;
+ float val = (settingfloat.get() - settingfloat.minimumValue)
+ / (settingfloat.maximumValue - settingfloat.minimumValue);
+ if (val < 0) {
+ val = 0;
+ }
+ text = (val * 100) + "%";
+ }
+ if (setting instanceof SettingText) {
+ SettingText settingtext = (SettingText) setting;
+ text = settingtext.get();
+ }
+ // Here's how you normally would set the TextArea's text, but we are
+ // going to use one of the helpers for it. It's still one line, but this
+ // way is a bit messy.
+ // ((SimpleTextAreaModel) textArea.getModel()).setText(text, false);
+ // Here's the cleaner way to do it, and this also works if you set it to
+ // use HTML mode.
+ GuiApiHelper.setTextAreaText(textArea, text);
+ }
+
+ /** The mod screen. */
+ public ModSettingScreen myModScreen;
+
+ /** The settings. */
+ public ModSettings mySettings;
+
+ /** The setting boolean d. */
+ public SettingBoolean settingBooleanD;
+
+ /** The setting boolean e. */
+ public SettingBoolean settingBooleanE;
+
+ /** The setting float b. */
+ public SettingFloat settingFloatB;
+
+ /** The setting int a. */
+ public SettingInt settingIntA;
+
+ /** The setting multi c. */
+ public SettingMulti settingMultiC;
+
+ /** The setting text f. */
+ public SettingText settingTextF;
+
+ /** The subscreen for booleans. */
+ public WidgetSimplewindow subscreenBooleans;
+
+ /** The subscreen for numberics. */
+ public WidgetSimplewindow subscreenNumberics;
+
+ /** The subscreen for others. */
+ public WidgetSimplewindow subscreenOthers;
+
+ @Override
+ public String getVersion() {
+ return "1.0";
+ }
+
+ @Override
+ public void load() {
+ // We need to set up our settings and modscreen, so let's do that.
+ mySettings = new ModSettings("mod_GuiApiIntermediateExample");
+ myModScreen = new ModSettingScreen("GuiAPI Intermediate Example");
+ // Now we are going to start setting up our subscreens. I want it in one
+ // column, so I'm using the WidgetSinglecolumn class. If you want to use
+ // the normal two column version, use WidgetClassicTwocolumn instead.
+ WidgetSinglecolumn numbericBaseWidget = new WidgetSinglecolumn();
+ // Note that this time we are saving the SettingInt that is returned.
+ // This is a faster way to get settings than the easy way shown in the
+ // basic example, but it takes up more space for field declarations. As
+ // well note instead of adding it to the modscreen, we are telling it to
+ // use the WidgetSinglecolumn we made just previous.
+ settingIntA = mySettings.addSetting(numbericBaseWidget,
+ "Nice Name for Int A", "backendIntA", 0, -100, 100);
+ // We are also going to have a TextArea display the percentage of what's
+ // selected. I'm leave the text blank for now, we will update it after
+ // we load the saved settings. As well, the reason for the false at the
+ // end is because we are just using simple text mode. If you want a
+ // TextBox to render HTML, set that to true.
+ TextArea textAreaA = GuiApiHelper.makeTextArea("", false);
+ // Add it to the subscreen we are making as well.
+ numbericBaseWidget.add(textAreaA);
+ // This is how you set up a callback when the value is changed. This
+ // code creates a ModAction that calls the static updateTextArea method
+ // in this class. It has two arguments: a TextArea and a Setting, so we
+ // specify that. Since there's no way for it to specify that kind of
+ // argument (The callback has no arguments to send), we are also going
+ // to tell it to use the references to the textarea we created for this
+ // setting, and the setting itself. After we create the setting, we use
+ // the setting to get a reference to the widget that is actually
+ // displayed, and add a callback.
+ settingIntA.displayWidget.addCallback(new ModAction(
+ mod_GuiApiIntermediateExample.class, "updateTextArea",
+ "Callback for TextArea A", TextArea.class, Setting.class)
+ .setDefaultArguments(textAreaA, settingIntA));
+ // You should already know what this is.
+ settingFloatB = mySettings.addSetting(numbericBaseWidget,
+ "Nice Name for Float B", "backendFloatB", 0f, -2.0f, 0.01f,
+ 2.0f);
+ TextArea textAreaB = GuiApiHelper.makeTextArea("", false);
+ numbericBaseWidget.add(textAreaB);
+ // And again, this is much the same. Note that for both of these
+ // ModActions, we specifically set a 'name' for it, so in case anything
+ // happens and an exception is thrown, you will see 'Callback for
+ // TextArea B' in the stack trace. This is useful for debugging, as
+ // usually you won't see what the method that actually crashed is, but
+ // now you can see what method it was, where the ModAction was created,
+ // etc. As long as you set your names that is.
+ settingFloatB.displayWidget.addCallback(new ModAction(
+ mod_GuiApiIntermediateExample.class, "updateTextArea",
+ "Callback for TextArea B", TextArea.class, Setting.class)
+ .setDefaultArguments(textAreaB, settingFloatB));
+ // Now we are going to merge the reset ModActions for the two 'numberic'
+ // settings.
+ ModAction mergedResetNumberics = new ModAction(settingIntA, "reset")
+ .mergeAction(new ModAction(settingFloatB, "reset"));
+ // And add a button for it to the subscreen.
+ numbericBaseWidget.add(GuiApiHelper.makeButton("Reset Numberic Values",
+ mergedResetNumberics, true));
+ // Now we finish creating the subscreen. The WidgetSimplewindow sets up
+ // a title bar on top, and a button bar to go back on the bottom. There
+ // is an option to disable the back button as well.
+ subscreenNumberics = new WidgetSimplewindow(numbericBaseWidget,
+ "Numberic Settings");
+ // And now create a Button to open your new SubScreen. We are going to
+ // use use the makeButton overloads this time, as we don't need to merge
+ // any ModActions. This one calls the static GuiModScreen.show method,
+ // which takes a Widget class as an parameter. When the button is
+ // clicked, it will call that method using subscreenNumberics as the
+ // argument.
+ myModScreen.append(GuiApiHelper.makeButton("Open Numberic Settings",
+ "show", GuiModScreen.class, true, new Class[] { Widget.class },
+ subscreenNumberics));
+ // And now we will do 2 other subscreens. There won't be anything
+ // special for these, so you should be able to follow along with what I
+ // am doing easily.
+ WidgetSinglecolumn booleanBaseWidget = new WidgetSinglecolumn();
+ settingBooleanD = mySettings.addSetting(booleanBaseWidget,
+ "Nice Name for Boolean D", "backendBooleanD", true);
+ settingBooleanE = mySettings.addSetting(booleanBaseWidget,
+ "Nice Name for Boolean E", "backendBooleanE", false, "Yes!",
+ "Noo!");
+ ModAction mergedResetBooleans = new ModAction(settingBooleanD, "reset")
+ .mergeAction(new ModAction(settingBooleanE, "reset"));
+ booleanBaseWidget.add(GuiApiHelper.makeButton("Reset Boolean Values",
+ mergedResetBooleans, true));
+ subscreenBooleans = new WidgetSimplewindow(booleanBaseWidget,
+ "Boolean Settings");
+ myModScreen.append(GuiApiHelper.makeButton("Open Boolean Settings",
+ "show", GuiModScreen.class, true, new Class[] { Widget.class },
+ subscreenBooleans));
+ WidgetSinglecolumn otherBaseWidget = new WidgetSinglecolumn();
+ settingMultiC = mySettings.addSetting(otherBaseWidget,
+ "Nice Name for Multi C", "backendMultiC", 0, "Option A",
+ "Option B", "Option C", "Option D", "Option E", "Option F");
+ // Actually, let's do something nice for the text widget. It's a bit
+ // small to see when editing, so we'll add a TextArea below it that
+ // shows what you have.
+ settingTextF = mySettings.addSetting(otherBaseWidget,
+ "Nice Name for Text F", "backendTextF",
+ "This is the Default Value");
+ TextArea textAreaF = GuiApiHelper.makeTextArea("", false);
+ otherBaseWidget.add(textAreaF);
+ // This line of code adds the TextArea to the override list so it won't
+ // be overridden to anything. Normally, WidgetClassicTwocolumn and
+ // WidgetSinglecolumn override the height and width of all widgets.
+ // There's a boolean to not do that for everything, but if you do, it
+ // makes buttons thin and ugly looking. So, we are going to add it
+ // specifically so it doesn't override the height. As well, you can use
+ // this to specify a height to override to, by changing the integer.
+ otherBaseWidget.heightOverrideExceptions.put(textAreaF, 0);
+ // And we'll add the callback. It's pretty much the same as the numberic
+ // callbacks, since in this case we are using a single method for each.
+ settingTextF.displayWidget.addCallback(new ModAction(
+ mod_GuiApiIntermediateExample.class, "updateTextArea",
+ "Callback for TextArea F", TextArea.class, Setting.class)
+ .setDefaultArguments(textAreaF, settingTextF));
+ ModAction mergedResetOthers = new ModAction(settingMultiC, "reset")
+ .mergeAction(new ModAction(settingTextF, "reset"));
+ otherBaseWidget.add(GuiApiHelper.makeButton("Reset Other Values",
+ mergedResetOthers, true));
+ subscreenOthers = new WidgetSimplewindow(otherBaseWidget,
+ "Other Settings");
+ myModScreen.append(GuiApiHelper.makeButton("Open Other Settings",
+ "show", GuiModScreen.class, true, new Class[] { Widget.class },
+ subscreenOthers));
+ myModScreen.append(GuiApiHelper.makeButton("Reset ALL settings",
+ "resetAll", mySettings, true));
+ // And as well, we'll use GuiApiHelper.createChoiceMenu for a reset menu
+ // as well.
+ Widget choiceMenu = GuiApiHelper
+ .createChoiceMenu(
+ "Which settings would you like to reset? You can pick to reset any of the groups from here, or you can also reset all the settings as once.",
+ true, true, "Reset the Numberic Settings.",
+ mergedResetNumberics, "Reset the Boolean Settings.",
+ mergedResetBooleans, "Reset the Other settings.",
+ mergedResetOthers, "Reset everything.", new ModAction(
+ mySettings, "resetAll"));
+ // And a button to show the choice menu.
+ myModScreen.append(GuiApiHelper.makeButton("Reset Settings with Menu",
+ "show", GuiModScreen.class, true, new Class[] { Widget.class },
+ choiceMenu));
+ mySettings.load();
+ // Finally, make sure all of those TextAreas are updated with the loaded
+ // values.
+ mod_GuiApiIntermediateExample.updateTextArea(textAreaA, settingIntA);
+ mod_GuiApiIntermediateExample.updateTextArea(textAreaB, settingFloatB);
+ mod_GuiApiIntermediateExample.updateTextArea(textAreaF, settingTextF);
+ }
+}
diff --git a/mcp/sharose/mods/guiapi/examples/mod_GuiApiItemTickExample.java b/mcp/sharose/mods/guiapi/examples/mod_GuiApiItemTickExample.java
new file mode 100644
index 0000000..651d020
--- /dev/null
+++ b/mcp/sharose/mods/guiapi/examples/mod_GuiApiItemTickExample.java
@@ -0,0 +1,194 @@
+ package sharose.mods.guiapi.examples;
+
+import sharose.mods.guiapi.ModAction;
+import sharose.mods.guiapi.ModSettingScreen;
+import sharose.mods.guiapi.ModSettings;
+import sharose.mods.guiapi.SettingBoolean;
+import sharose.mods.guiapi.SettingInt;
+import sharose.mods.guiapi.WidgetItem2DRender;
+import sharose.mods.guiapi.WidgetTick;
+import net.minecraft.item.Item;
+import net.minecraft.item.ItemStack;
+import net.minecraft.src.BaseMod;
+
+/**
+ * This is an example specifically focused to some usage of {@link WidgetTick}
+ * and {@link WidgetItem2DRender}. We'll be making a {@link ModSettingScreen} that lets
+ * you select an Item ID and a damage value to display, as well as making it so
+ * that if the selected Item ID is a SubItem, if a toggle is on it will
+ * automatically cycle it once a second.
+ *
+ * @author ShaRose
+ */
+public class mod_GuiApiItemTickExample extends BaseMod {
+
+ /** The mod screen. */
+ public ModSettingScreen myModScreen;
+
+ /** The settings. */
+ public ModSettings mySettings;
+
+ /** The setting boolean Auto Cycle SubItems. */
+ public SettingBoolean settingBooleanAutoCycleSubItems;
+
+ /** The setting int for Item Damage. */
+ public SettingInt settingIntItemDamage;
+
+ /** The setting int Item ID. */
+ public SettingInt settingIntItemID;
+
+ /** The {@link WidgetItem2DRender} this uses to render Item Icons. */
+ public WidgetItem2DRender widgetRenderer;
+
+ /**
+ * This {@link WidgetTick} is used to call {@link #onTick()} every 1000ms (1
+ * second).
+ */
+ public WidgetTick widgetTicker;
+
+ @Override
+ public String getVersion() {
+ return "1.0";
+ }
+
+ @Override
+ public void load() {
+ // Create the ModSettings, ModSettingScreen, and set the
+ // ModSettingScreen to use a single column, like in the intermediate
+ // examples.
+ mySettings = new ModSettings("mod_GuiApiItemTickExample");
+ myModScreen = new ModSettingScreen("GuiAPI Item / Tick Example");
+ myModScreen.setSingleColumn(true);
+
+ // Create a ModAction for the onUpdate() method.
+ ModAction onUpdateAction = new ModAction(this, "onUpdate");
+
+ // Create a SettingInt with a range between 1 and the max Item id, and
+ // then add it to the ModSettingScreen using a ModSettings helper.
+ settingIntItemID = mySettings.addSetting(myModScreen,
+ "Selected Item ID", "settingIntItemID", 1, 1,
+ Item.itemsList.length - 1);
+
+ // Add a callback so whenever it's changed, it calls that onUpdate
+ // ModAction from earlier.
+ settingIntItemID.displayWidget.addCallback(onUpdateAction);
+
+ // Create a SettingInt with the full range of a Short (Damage is saved
+ // as a Short for ItemStacks), and then add it to the ModSettingScreen
+ // using a ModSettings helper.
+ settingIntItemDamage = mySettings.addSetting(myModScreen,
+ "SubItem / Damage", "settingIntItemDamage", 0, Short.MIN_VALUE,
+ Short.MAX_VALUE);
+
+ // Add a callback so whenever it's changed, it calls that onUpdate
+ // ModAction from earlier.
+ settingIntItemDamage.displayWidget.addCallback(onUpdateAction);
+
+ // Create a SettingBoolean to enable / disable automated SubItem
+ // cycling, and then add it to the ModSettingScreen using a ModSettings
+ // helper.
+ settingBooleanAutoCycleSubItems = mySettings.addSetting(myModScreen,
+ "Automatically cycle SubItems",
+ "settingBooleanAutoCycleSubItems", true, "Yes", "No");
+
+ // Make a new WidgetItem2DRender.
+ widgetRenderer = new WidgetItem2DRender(new ItemStack(1, 1, 0));
+
+ // Add it to the ModSettingScreen.
+ myModScreen.append(widgetRenderer);
+
+ // Now create a new WidgetTick.
+ widgetTicker = new WidgetTick();
+
+ // Add it to myModScreen.theWidget. It will be a WidgetSimplewindow by
+ // default, so you can do the same for any SubScreens you use.
+ // Preferably do NOT add this to any layout columns like
+ // WidgetClassicTwocolumn or WidgetSinglecolumn, as it will draw a space
+ // for the WidgetTick. We don't want it drawn, so we'll add it to a
+ // widget that won't add it to the layout.
+ myModScreen.theWidget.add(widgetTicker);
+
+ // Create a ModAction for onTick, and then add it to the WidgetTick we
+ // created so it will call it every 1000 milliseconds, or 1 second. For
+ // example, to make it 'tick' twice a second, the second argument would
+ // be 500. If you wanted it to call every frame, make it 0.
+ widgetTicker.addCallback(new ModAction(this, "onTick"), 1000);
+
+ // Load any saved settings, if any. This will call the onUpdate method
+ // automatically.
+ mySettings.load();
+ }
+
+ /**
+ * This method is called every 1000ms (1 second) by {@link #widgetTicker}.
+ */
+ @SuppressWarnings("unused")
+ private void onTick() {
+ // Are we supposed to cycle SubItems in the first place? If not, return.
+ if (!settingBooleanAutoCycleSubItems.get()) {
+ return;
+ }
+
+ // Get the current stack.
+ ItemStack stack = new ItemStack(settingIntItemID.get(), 1,
+ settingIntItemDamage.get());
+
+ // Is the Item null, or is it NOT a subtype? If so, return.
+ if ((stack.getItem() == null) || !stack.getHasSubtypes()) {
+ return;
+ }
+
+ // Get the damage.
+ int value = stack.getItemDamage();
+
+ if ((value >= 31) || (value < 0)) {
+ value = 0; // If greater than or equal to 31 or lower than 0, make
+ // it 0.
+ } else {
+ value++; // Else, increase.
+ }
+
+ // And set settingIntItemDamage's value. This will also call onUpdate()
+ // for us, since it will call settingIntItemDamage's callbacks.
+ settingIntItemDamage.set(value);
+ }
+
+ /**
+ * This is called whenever {@link #settingIntItemID} or
+ * {@link #settingIntItemDamage} change. It sets max / min bounds depending
+ * on if it's a subitem or not, and updates {@link #widgetRenderer} so it
+ * renders with the new Item ID and Damage value.
+ */
+ @SuppressWarnings("unused")
+ private void onUpdate() {
+ // Get the current stack.
+ ItemStack stack = new ItemStack(settingIntItemID.get(), 1,
+ settingIntItemDamage.get());
+ // Is the current item not null, AND have the SubTypes flag set?
+ if ((stack.getItem() != null) && stack.getHasSubtypes()) {
+ // If yes, set the range as 0 - 31. Normally SubItems only use 0 -
+ // 15, to keep compatibility with blocks, and as well there are a
+ // few ways of trying to find the max SubItem index for any given
+ // Item, but they don't always work, so we are hardcoding it for
+ // this demo's purposes.
+ settingIntItemDamage.maximumValue = 31;
+ settingIntItemDamage.minimumValue = 0;
+ // Make sure to update the Display Widget so it reflects the correct
+ // range. Note, this WILL cap the value if needed.
+ settingIntItemDamage.displayWidget.update();
+ } else {
+ // If not, set the range as the same as a Short. This is what
+ // ItemStack saves damage as.
+ settingIntItemDamage.maximumValue = Short.MAX_VALUE;
+ settingIntItemDamage.minimumValue = Short.MIN_VALUE;
+ // Make sure to update the Display Widget so it reflects the correct
+ // range. Note, this WILL cap the value if needed.
+ settingIntItemDamage.displayWidget.update();
+ }
+ // Finally, set the Render Stack. Note, that if the Item is null, the
+ // WidgetItem2DRender will just not render anything, which is what we
+ // want.
+ widgetRenderer.setRenderStack(stack);
+ }
+
+}
diff --git a/mcp/sharose/mods/guiapi/examples/mod_GuiApiTWLExamples.java b/mcp/sharose/mods/guiapi/examples/mod_GuiApiTWLExamples.java
new file mode 100644
index 0000000..ba930fe
--- /dev/null
+++ b/mcp/sharose/mods/guiapi/examples/mod_GuiApiTWLExamples.java
@@ -0,0 +1,199 @@
+package sharose.mods.guiapi.examples;
+
+import java.util.Random;
+
+import sharose.mods.guiapi.GuiApiFontHelper;
+import sharose.mods.guiapi.GuiApiHelper;
+import sharose.mods.guiapi.GuiModScreen;
+import sharose.mods.guiapi.ModAction;
+import sharose.mods.guiapi.ModSettingScreen;
+import sharose.mods.guiapi.ModSettings;
+import sharose.mods.guiapi.SettingList;
+import sharose.mods.guiapi.SettingText;
+import sharose.mods.guiapi.WidgetList;
+import sharose.mods.guiapi.WidgetSimplewindow;
+import sharose.mods.guiapi.WidgetSingleRow;
+import sharose.mods.guiapi.WidgetSinglecolumn;
+import sharose.mods.guiapi.WidgetText;
+import sharose.mods.guiapi.GuiApiFontHelper.FontStates;
+
+import net.minecraft.src.BaseMod;
+import de.matthiasmann.twl.ColorSelector;
+import de.matthiasmann.twl.Label;
+import de.matthiasmann.twl.ListBox;
+import de.matthiasmann.twl.ProgressBar;
+import de.matthiasmann.twl.Widget;
+import de.matthiasmann.twl.model.ColorSpaceHSL;
+
+public class mod_GuiApiTWLExamples extends BaseMod {
+
+ @SuppressWarnings("unused")
+ private static void addRandomListboxOption(SettingList setting) {
+ Random rand = new Random();
+ setting.get().add("Option " + rand.nextInt(10000));
+ setting.displayWidget.update();
+ }
+
+ @SuppressWarnings("unused")
+ private static void removeSelectedListboxOption(SettingList setting) {
+
+ ListBox listbox = ((WidgetList) setting.displayWidget).listBox;
+ int selected = listbox.getSelected();
+ if (selected == -1) {
+ return;
+ }
+ setting.get().remove(selected);
+ setting.displayWidget.update();
+
+ if (selected == listbox.getNumEntries()) // I'm only removing one at a
+ // time, so this is OK.
+ {
+ selected--;
+ }
+ if (selected == -1) {
+ return; // This is if there aren't any entries to select left. I
+ // could also check getNumEntries to see if it's 0.
+ }
+
+ listbox.setSelected(selected);
+ }
+
+ @SuppressWarnings("unused")
+ private static void showSelectedListboxOption(SettingList setting) {
+ GuiModScreen.show(GuiApiHelper.makeTextDisplayAndGoBack(
+ "ListBox Status", setting.displayWidget.userString(),
+ "Go Back", false));
+ }
+
+ private WidgetText colorEditField;
+ private GuiApiFontHelper colorFontHelper;
+ private Label colorLabel;
+ private ProgressBar colorProgressBar;
+ private ColorSelector colorSelector;
+ private SettingList listBoxSettingTest;
+ private ModSettingScreen modScreen;
+
+ private ModSettings modSettings;
+
+ private WidgetSimplewindow screenColoringWindow;
+
+ private WidgetSimplewindow screenListBoxTest;
+
+ @Override
+ public String getVersion() {
+ return "1.0";
+ }
+
+ @Override
+ public void load() {
+ modSettings = new ModSettings("mod_GuiApiTWLExamples");
+ SetUpColouringWindow();
+
+ SetUpListBox();
+
+ SetUpModWindow();
+ }
+
+ private void SetUpColouringWindow() {
+ colorFontHelper = new GuiApiFontHelper();
+ WidgetSinglecolumn widgetSingleColumn = new WidgetSinglecolumn();
+ widgetSingleColumn.childDefaultWidth = 300;
+
+ colorLabel = new Label("This is an example of coloring a label's Text.");
+ widgetSingleColumn.add(colorLabel);
+ colorFontHelper.setFont(colorLabel);
+ colorProgressBar = new ProgressBar();
+ colorProgressBar.setValue(0.7f);
+ colorProgressBar.setTheme("/progressbar");
+ // This sets the theme manually. I'm not currently sure why but unless
+ // you set this it won't actually render the progress bar part, only the
+ // text. You can use this to change it somewhat though. The available
+ // themes for this are:
+ // /progressbar - The standard.
+ // /progressbar-white - Changes the progress image to a plain white
+ // area.
+ // /progressbar-noback - Removes the background image.
+ // /progressbar-white-noback - Changes the progress image to a plain
+ // white area and removes the background image.
+ colorProgressBar.setText("Coloring Progressbar!");
+ widgetSingleColumn.add(colorProgressBar);
+ colorFontHelper.setFont(colorProgressBar);
+
+ widgetSingleColumn.heightOverrideExceptions.put(colorProgressBar, 30);
+ colorEditField = new WidgetText(
+ new SettingText("dummyText", "Edit Me!"), null);
+ widgetSingleColumn.add(colorEditField);
+ colorFontHelper.setFont(colorEditField);
+ widgetSingleColumn.heightOverrideExceptions.put(colorEditField, 30);
+
+ colorSelector = new ColorSelector(new ColorSpaceHSL());
+ colorSelector.setShowAlphaAdjuster(false);
+ colorSelector.setShowNativeAdjuster(true);
+ colorSelector.setShowRGBAdjuster(true);
+ colorSelector.setShowPreview(true);
+ colorSelector.addCallback(new ModAction(this, "updateColors",
+ "Updates the colors for the 'color' window."));
+ colorSelector.setColor(colorFontHelper.getColor(FontStates.normal));
+ widgetSingleColumn.add(colorSelector);
+ widgetSingleColumn.heightOverrideExceptions.put(colorSelector, 0);
+
+ screenColoringWindow = new WidgetSimplewindow(widgetSingleColumn,
+ "GuiAPI / TWL Coloring examples!");
+ }
+
+
+ private void SetUpListBox() {
+ WidgetSinglecolumn widgetSingleColumn = new WidgetSinglecolumn();
+ listBoxSettingTest = modSettings.addSetting(widgetSingleColumn,
+ "ListBox Test One", "listboxTest1", "Option 1", "Option 2",
+ "Option 3", "Option 4", "Option 5", "Option 6");
+ ((WidgetList) listBoxSettingTest.displayWidget).listBox
+ .setTheme("/listbox");
+ widgetSingleColumn.heightOverrideExceptions.put(
+ listBoxSettingTest.displayWidget, 140);
+
+ WidgetSingleRow listBoxRow = new WidgetSingleRow(110, 20);
+
+ listBoxRow
+ .add(GuiApiHelper.makeButton("Add Random", new ModAction(this,
+ "addRandomListboxOption", SettingList.class)
+ .setDefaultArguments(listBoxSettingTest), true));
+
+ listBoxRow.add(GuiApiHelper.makeButton("Display Selected",
+ new ModAction(this, "showSelectedListboxOption",
+ SettingList.class)
+ .setDefaultArguments(listBoxSettingTest), true));
+
+ listBoxRow.add(GuiApiHelper.makeButton("Remove Selected",
+ new ModAction(this, "removeSelectedListboxOption",
+ SettingList.class)
+ .setDefaultArguments(listBoxSettingTest), true));
+
+ widgetSingleColumn.add(listBoxRow);
+ widgetSingleColumn.widthOverrideExceptions.put(listBoxRow, 0);
+ screenListBoxTest = new WidgetSimplewindow(widgetSingleColumn,
+ "GuiAPI / TWL ListBox example!");
+ }
+
+ private void SetUpModWindow() {
+ modSettings.load();
+ modScreen = new ModSettingScreen("GuiAPI TWL Examples");
+ if (screenColoringWindow != null) {
+ modScreen.append(GuiApiHelper.makeButton("Open Coloring Examples",
+ "show", GuiModScreen.class, true,
+ new Class[] { Widget.class }, screenColoringWindow));
+ }
+
+ if (screenListBoxTest != null) {
+ modScreen.append(GuiApiHelper.makeButton("Open Listbox Example",
+ "show", GuiModScreen.class, true,
+ new Class[] { Widget.class }, screenListBoxTest));
+ }
+ }
+
+ @SuppressWarnings("unused")
+ private void updateColors() {
+ colorFontHelper.setColor(FontStates.normal, colorSelector.getColor());
+ }
+
+}
diff --git a/newtheme b/newtheme
deleted file mode 160000
index 80e3c47..0000000
--- a/newtheme
+++ /dev/null
@@ -1 +0,0 @@
-Subproject commit 80e3c47b96b81b08593cc9dd099ae8ff5590d646
diff --git a/newtheme/Eforen.png b/newtheme/Eforen.png
new file mode 100644
index 0000000..6c6260e
Binary files /dev/null and b/newtheme/Eforen.png differ
diff --git a/newtheme/Eforen.xml b/newtheme/Eforen.xml
new file mode 100644
index 0000000..7305c14
--- /dev/null
+++ b/newtheme/Eforen.xml
@@ -0,0 +1,394 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/newtheme/EforenArrows.png b/newtheme/EforenArrows.png
new file mode 100644
index 0000000..25e9584
Binary files /dev/null and b/newtheme/EforenArrows.png differ
diff --git a/newtheme/TWL Logo.png b/newtheme/TWL Logo.png
new file mode 100644
index 0000000..ffff7f1
Binary files /dev/null and b/newtheme/TWL Logo.png differ
diff --git a/newtheme/chaos_sphere_blue_800x600.png b/newtheme/chaos_sphere_blue_800x600.png
new file mode 100644
index 0000000..e97708c
Binary files /dev/null and b/newtheme/chaos_sphere_blue_800x600.png differ
diff --git a/newtheme/cursors.png b/newtheme/cursors.png
new file mode 100644
index 0000000..ad105af
Binary files /dev/null and b/newtheme/cursors.png differ
diff --git a/newtheme/cursors.xml b/newtheme/cursors.xml
new file mode 100644
index 0000000..a3f9f71
--- /dev/null
+++ b/newtheme/cursors.xml
@@ -0,0 +1,18 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/newtheme/font.fnt b/newtheme/font.fnt
new file mode 100644
index 0000000..8b3120c
--- /dev/null
+++ b/newtheme/font.fnt
@@ -0,0 +1,116 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/theme/font/default.png b/newtheme/font/default.png
similarity index 100%
rename from theme/font/default.png
rename to newtheme/font/default.png
diff --git a/newtheme/font_00.png b/newtheme/font_00.png
new file mode 100644
index 0000000..ae2b88b
Binary files /dev/null and b/newtheme/font_00.png differ
diff --git a/newtheme/gui.png b/newtheme/gui.png
new file mode 100644
index 0000000..6d09238
Binary files /dev/null and b/newtheme/gui.png differ
diff --git a/newtheme/gui.xml b/newtheme/gui.xml
new file mode 100644
index 0000000..12a950b
--- /dev/null
+++ b/newtheme/gui.xml
@@ -0,0 +1,155 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/theme/gui/background.png b/newtheme/gui/background.png
similarity index 100%
rename from theme/gui/background.png
rename to newtheme/gui/background.png
diff --git a/theme/gui/container.png b/newtheme/gui/container.png
similarity index 100%
rename from theme/gui/container.png
rename to newtheme/gui/container.png
diff --git a/theme/gui/crafting.png b/newtheme/gui/crafting.png
similarity index 100%
rename from theme/gui/crafting.png
rename to newtheme/gui/crafting.png
diff --git a/theme/gui/furnace.png b/newtheme/gui/furnace.png
similarity index 100%
rename from theme/gui/furnace.png
rename to newtheme/gui/furnace.png
diff --git a/theme/gui/gui.png b/newtheme/gui/gui.png
similarity index 100%
rename from theme/gui/gui.png
rename to newtheme/gui/gui.png
diff --git a/theme/gui/icons.png b/newtheme/gui/icons.png
similarity index 100%
rename from theme/gui/icons.png
rename to newtheme/gui/icons.png
diff --git a/theme/gui/inventory.png b/newtheme/gui/inventory.png
similarity index 100%
rename from theme/gui/inventory.png
rename to newtheme/gui/inventory.png
diff --git a/theme/gui/items.png b/newtheme/gui/items.png
similarity index 100%
rename from theme/gui/items.png
rename to newtheme/gui/items.png
diff --git a/theme/gui/logo.png b/newtheme/gui/logo.png
similarity index 100%
rename from theme/gui/logo.png
rename to newtheme/gui/logo.png
diff --git a/theme/gui/particles.png b/newtheme/gui/particles.png
similarity index 100%
rename from theme/gui/particles.png
rename to newtheme/gui/particles.png
diff --git a/theme/gui/slot.png b/newtheme/gui/slot.png
similarity index 100%
rename from theme/gui/slot.png
rename to newtheme/gui/slot.png
diff --git a/theme/gui/trap.png b/newtheme/gui/trap.png
similarity index 100%
rename from theme/gui/trap.png
rename to newtheme/gui/trap.png
diff --git a/theme/gui/unknown_pack.png b/newtheme/gui/unknown_pack.png
similarity index 100%
rename from theme/gui/unknown_pack.png
rename to newtheme/gui/unknown_pack.png
diff --git a/newtheme/guiTheme.xml b/newtheme/guiTheme.xml
new file mode 100644
index 0000000..0b93402
--- /dev/null
+++ b/newtheme/guiTheme.xml
@@ -0,0 +1,468 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ctrl A
+ ctrl X
+ ctrl C
+ ctrl V
+
+
+
+ none
+ none
+ normal
+ left
+ 0
+ 0
+ 0
+ 0
+ eforen.cursor.normal
+ -defaultInputMap
+
+
+
+ left
+
+
+
+ editfield.*
+ eforen.editfield.*
+ background.border
+ 0x25CF
+ 32767
+ 5
+
+ eforen.cursor.text
+ 150
+
+
+
+ eforen.button.*
+ background.border
+ center
+ 56
+
+
+
+ eforen.togglebutton.*
+ background.border
+
+
+
+
+
+ eforen.checkbox.*
+
+
+
+ eforen.progressbar.*
+ background.border
+ 100
+
+
+
+ eforen.progressbar.progressImage-glow
+
+
+
+ eforen.progressbar.progressImage-glow-anim
+
+
+
+ eforen.hscrollbar.background
+
+ eforen.hscrollbar.leftbutton.*
+
+
+ eforen.hscrollbar.rightbutton.*
+
+
+ eforen.hscrollbar.thumb.*
+
+ true
+ 106
+
+
+
+ eforen.vscrollbar.background
+
+ eforen.vscrollbar.upbutton.*
+
+
+ eforen.vscrollbar.downbutton.*
+
+
+ eforen.vscrollbar.thumb.*
+
+ true
+ 106
+
+
+
+
+ listbox.background
+ 22
+ SINGLE_COLUMN
+ true
+ false
+ false
+ 8000
+ 8000
+
+
+ listbox.display.*
+ listbox-display
+ 1,2,1,2
+
+
+
+
+ eforen.combobox.background
+ 150
+
+ eforen.combobox.display.selection
+ 4,8,4,8
+ 100
+ left
+
+
+ eforen.combobox.button.*
+
+
+
+
+
+ eforen.combobox.listbox.background
+
+
+
+
+ valuadjuster.button.*
+ background.border
+
+
+
+
+ -
+
+
+ +
+
+
+ center
+
+
+ valuadjuster.edit.background
+
+ 100
+ true
+
+
+
+
+
+
+ 100
+ 100
+ 32767
+ 32767
+ true
+ 5
+ 10
+
+ scrollpane.dragbutton.*
+
+
+
+
+
+
+
+
+
+
+ box
+ background.border
+
+
+ box.separator
+ background.border
+
+ none
+
+
+ none
+ pathbutton.*
+
+
+
+
+
+ tooltip.background
+ background.border
+ 250
+
+
+
+
+ 8,8
+ 12,12
+ 20,20
+ 25,25
+
+
+
+
+
+
+ colorselector.*
+ 2
+ 30
+ 128
+
+
+ colorselector.*
+ 2
+ 128
+ 128
+
+
+ 32767
+
+
+ Current color:
+
+
+ 2
+ 64
+ 64
+
+ white
+
+
+ eforen.cursor.arrow.*
+
+
+
+ eforen.frame.resizeable
+ background.border
+ 5
+ 5
+ -5
+ 5
+ false
+ 0
+ 0
+ false
+ 0
+ 0
+ eforen.cursor.arrow.*
+ #F888
+ 200
+ 200
+ 200
+ 200
+
+ top
+
+
+ eforen.frame.closebutton.*
+ background.border
+ 0
+
+
+
+
+
+ eforen.frame.resizeable-title
+ background.border
+ 10
+ 30
+ -70
+ 27
+ true
+ -28
+ 15
+
+
+
+ eforen.graph.background
+ background.border
+ 100
+ 100
+ 5
+ 8
+
+
+ none
+
+
+
+
+ 13
+ 20,19
+
+
+ none
+ none
+ table.*
+ table.*
+ 20
+
+
+
+
+
+
+ eforen.tableheader.background
+
+ 256
+ 20
+ 3
+ eforen.cursor.arrow.left
+ none
+
+
+ ctrl SPACE
+
+
+
+ 20
+
+
+ 20
+
+
+
+
+
+ box
+ background.border
+
+
+
+
+ 0
+
+
+
+
+
+
+
+
+ none
+
+
+ Animate images
+ Animate the images while they are displayed
+
+
+ editfield.*
+ eforen.editfield.*
+ background.border
+
+ none
+ 0
+
+ topleft
+
+
+ 100
+ 100
+
+
+
+
+ none
+
+
+ Accept
+
+
+ Cancel
+
+
+
+
+ eforen.frame.fixed
+ background.border
+
+
+
+
+ gui.background
+
+
+
+
+ 0,0,0,10
+
+ twl-logo
+ textarea.ul-bullet
+
+ 400
+
+
+ 42,25,26,15
+
+
+
+
+
+
+
+
+
+
+
+
+
+ center
+
+
+
+ white
+ center
+
+
+
+
+
+
+
+ 5
+ CENTER
+
+
diff --git a/newtheme/simple.png b/newtheme/simple.png
new file mode 100644
index 0000000..63daa3c
Binary files /dev/null and b/newtheme/simple.png differ
diff --git a/newtheme/simple.xml b/newtheme/simple.xml
new file mode 100644
index 0000000..fb41fdd
--- /dev/null
+++ b/newtheme/simple.xml
@@ -0,0 +1,1121 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ctrl A
+ ctrl X
+ ctrl C
+ ctrl V
+
+
+
+ none
+ none
+ normal
+ left
+ 0
+ 0
+ 0
+ 0
+ -defaultInputMap
+
+
+
+ button.*
+ background.border
+ center
+
+
+
+ togglebutton.*
+ background.border
+
+
+
+ radiobutton.*
+ background.border
+
+
+
+ checkbox.*
+ 13
+ 13
+
+
+
+
+
+
+ editfield.*
+ background.border
+ 0x25CF
+ 32767
+ 5
+
+ cursor.text
+ 150
+
+
+ -borderG
+ background.border
+
+
+
+
+ -borderG
+ background.border
+ 0
+
+
+
+
+ scrollbar.background
+
+ hscrollbar.leftbutton.*
+ 16
+
+
+ hscrollbar.rightbutton.*
+ 16
+
+
+ scrollbar.thumb.*
+
+ true
+ 48
+ 16
+
+
+
+ scrollbar.background
+
+ vscrollbar.upbutton.*
+ 16
+
+
+ vscrollbar.downbutton.*
+ 16
+
+
+ scrollbar.thumb.*
+
+ true
+ 16
+ 48
+
+
+
+
+
+
+ -button-depressed
+ background.border
+ font.lineHeight + 4
+ SINGLE_COLUMN
+ true
+ false
+ false
+ 8000
+ 8000
+
+
+ listbox.display.*
+ listbox-display
+ 2
+
+
+
+
+ -button-depressed
+
+ combobox.display.*
+ 3
+ 50
+ left
+ combobox
+
+
+ combobox.button.*
+ 18
+
+
+
+
+ -borderH
+ 2
+
+ none
+ 0
+
+ 150
+
+
+
+
+
+ 100
+ 100
+ 32767
+ 32767
+ false
+ 5
+ 10
+
+
+
+
+
+ splitpane.splitter.*
+ background.border
+ cursor.resizecolumn
+
+
+ splitpane.vsplitter.*
+ background.border
+ cursor.resizerow
+
+
+
+
+ 5,5
+ 8,8
+ 10,10
+ 15,15
+
+
+
+
+ -button-depressed
+ background.border
+
+ normal
+
+
+
+
+
+
+
+
+ 15
+ 14,11
+
+
+ font.lineHeight + 6
+
+ listbox-display
+ 0,3,0,0
+
+
+
+
+
+ propertysheet.sublist.*
+
+
+
+
+ treetable.treebutton.*
+
+
+ columnHeader.*
+
+ 256
+ 20
+ 3
+ cursor.resizecolumn
+ none
+
+ table.row.*
+
+ ctrl SPACE
+ DOWN
+ UP
+ NEXT
+ PRIOR
+ HOME
+ END
+ shift DOWN
+ shift UP
+ shift NEXT
+ shift PRIOR
+ shift HOME
+ shift END
+ ctrl DOWN
+ ctrl UP
+ ctrl NEXT
+ ctrl PRIOR
+ ctrl HOME
+ ctrl END
+ ADD
+ SUBTRACT
+
+
+
+ 20
+
+
+ 20
+
+
+
+
+ -borderH
+ 10
+
+
+
+ 20
+
+
+
+
+
+
+ 0
+
+ treepathdisplay.node.*
+
+
+ cursor.text
+ 2,0
+
+ node.font.lineHeight + 4
+ center
+ cursor.text
+
+
+
+
+
+ -button-depressed
+ treecomboboxPopup
+
+ combobox.display.*
+ 50
+
+ 3
+
+ combobox
+
+
+ combobox
+
+
+
+
+ combobox.button.*
+ 18
+
+
+
+ -borderH
+ 2
+
+
+ none
+ 0
+ 0
+
+
+ 300
+
+
+
+ -borderG
+
+
+
+
+
+
+ 32767
+
+
+ fileselector.buttonOneLevelUp.*
+ 21
+
+
+ fileselector.buttonHome.*
+ 21
+
+
+ fileselector.buttonMRU.*
+ 20
+ Shows recently used folders
+
+
+ fileselector.buttonMRU.*
+ 20
+ Shows recently opened files
+
+
+ OK
+ 80
+
+
+ Cancel
+ 80
+
+
+ fileselector.buttonShowFolders.*
+ Shows folders in the file table
+ 20
+ 20
+
+
+ fileselector.buttonShowHidden.*
+ Shows hidden or system files
+ 20
+ 20
+
+
+ fileselector.buttonRefresh.*
+ Refreshes the file table
+ 20
+ 20
+
+
+ -button-depressed
+ 2
+
+
+ 70
+ 80
+ 140
+
+
+
+
+
+ BACK
+ RETURN
+
+
+
+ 20
+ 18
+
+
+
+ -borderH
+ 2
+
+ none
+ 0
+
+
+
+ -borderH
+ 2
+
+
+ none
+ 0
+
+
+ OK
+ 80
+
+
+ Cancel
+ 80
+
+
+
+
+
+ -button-depressed
+ background.border
+ 100
+ 100
+ 1
+ 1
+
+
+ black
+ 1.0
+
+
+
+
+
+
+ none
+
+ -
+ 0
+ 16
+
+
+ +
+ 0
+ 16
+
+
+ valueadjuster.display.*
+ center
+
+
+ 100
+ true
+
+
+
+
+ -borderE
+ -gradC
+ 2
+ 100
+
+
+
+
+
+
+
+
+ -button-depressed
+ 2
+ 30
+ 128
+ colorselector.*
+
+
+ -button-depressed
+ 2
+ 128
+ 128
+ colorselector.*
+
+
+ 32767
+
+
+ Current color:
+
+
+ -button-depressed
+ 2
+ 64
+ 64
+
+ white
+
+
+
+ Current color in ARGB hex format
+
+
+
+
+ frame.resizeable-title
+ background.border
+ 4
+ 6
+ -24
+ 20
+ true
+ -22
+ 6
+ false
+ -18
+ -18
+ white
+ 0
+ 0
+ 0
+ 0
+ arrow.*
+
+ left
+
+
+
+
+
+ frame.closebutton.*
+ 16
+ 14
+
+
+ frame.resizeIcon
+
+
+
+ -borderA
+ background.border
+ false
+ 4
+
+
+ true
+
+
+
+
+
+
+ popupmenu.button.*
+ popupmenu-button
+ 5,2
+
+
+
+ -borderA
+ background.border
+
+
+
+ popupmenu.separator
+
+
+ 0
+
+
+
+
+
+
+
+ 0
+ BOTTOMLEFT
+
+
+
+ CTRL TAB
+ CTRL SHIFT TAB
+
+
+
+
+
+
+
+
+
+
+
+ none
+
+ twl-logo
+ textarea.ul-bullet
+
+ 400
+
+
+ -button-depressed
+ background.border
+
+
+
+
+
+
+
+
+ -button-depressed
+ 2
+
+
+
+ none
+ 4
+
+
+ editfield.*
+ background.border
+
+ none
+ 0
+
+ topleft
+
+
+ 100
+ 100
+
+
+
+
+
+
+
+ image
+
+
+
+
+
+
+ Accept
+
+
+ Cancel
+
+
+
+ -borderA
+ 6
+
+
+
+
+ buttonBox.background
+ 3,2,1,2
+
+ 5
+ BOTTOM
+
+
+
+ -button-normal
+ background.border
+
+ -button-depressed
+ background.border
+
+
+
+ blockgame0
+ blockgame1
+ blockgame2
+ blockgame3
+ blockgame4
+ blockgame5
+ blockgame6
+ blockgame7
+
+
+
+
+
+
+
+
+
+ black
+ center
+
+
+
+ white
+ center
+
+
+
+
+
+
+
+ -button-depressed
+ background.border
+ 0
+ 150
+
+ none
+ 384
+ 400
+
+
+
+
+
+
+
+ -button-depressed
+ background.border
+
+ none
+ 10
+
+
+
+
diff --git a/newtheme/simple.xml.old b/newtheme/simple.xml.old
new file mode 100644
index 0000000..b420e81
--- /dev/null
+++ b/newtheme/simple.xml.old
@@ -0,0 +1,1106 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ctrl A
+ ctrl X
+ ctrl C
+ ctrl V
+
+
+
+ none
+ none
+ normal
+ left
+ 0
+ 0
+ 0
+ 0
+ -defaultInputMap
+
+
+
+ button.*
+ background.border
+ center
+
+
+
+ togglebutton.*
+ background.border
+
+
+
+ radiobutton.*
+ background.border
+
+
+
+ checkbox.*
+ 13
+ 13
+
+
+
+
+
+
+ editfield.*
+ background.border
+ 0x25CF
+ 32767
+ 5
+
+ cursor.text
+ 150
+
+
+ -borderG
+ background.border
+
+
+
+
+ -borderG
+ background.border
+ 0
+
+
+
+
+ scrollbar.background
+
+ hscrollbar.leftbutton.*
+ 16
+
+
+ hscrollbar.rightbutton.*
+ 16
+
+
+ scrollbar.thumb.*
+
+ true
+ 48
+ 16
+
+
+
+ scrollbar.background
+
+ vscrollbar.upbutton.*
+ 16
+
+
+ vscrollbar.downbutton.*
+ 16
+
+
+ scrollbar.thumb.*
+
+ true
+ 16
+ 48
+
+
+
+
+
+
+ -borderC
+ background.border
+ font.lineHeight + 4
+ SINGLE_COLUMN
+ true
+ false
+ false
+ 8000
+ 8000
+
+
+ listbox.display.*
+ listbox-display
+ 2
+
+
+
+
+ -borderC
+
+ combobox.display.*
+ 3
+ 50
+ left
+ combobox
+
+
+ combobox.button.*
+ 18
+
+
+
+
+ -borderH
+ 2
+
+ none
+ 0
+
+ 150
+
+
+
+
+
+ 100
+ 100
+ 32767
+ 32767
+ false
+ 5
+ 10
+
+
+
+
+
+ splitpane.splitter.*
+ background.border
+ cursor.resizecolumn
+
+
+ splitpane.vsplitter.*
+ background.border
+ cursor.resizerow
+
+
+
+
+ 5,5
+ 8,8
+ 10,10
+ 15,15
+
+
+
+
+ -borderC
+ background.border
+
+ normal
+
+
+
+
+
+
+
+
+ 15
+ 14,11
+
+
+ font.lineHeight + 6
+
+ listbox-display
+ 0,3,0,0
+
+
+
+
+
+ propertysheet.sublist.*
+
+
+
+
+ treetable.treebutton.*
+
+
+ columnHeader.*
+
+ 256
+ 20
+ 3
+ cursor.resizecolumn
+ none
+
+ table.row.*
+
+ ctrl SPACE
+ DOWN
+ UP
+ NEXT
+ PRIOR
+ HOME
+ END
+ shift DOWN
+ shift UP
+ shift NEXT
+ shift PRIOR
+ shift HOME
+ shift END
+ ctrl DOWN
+ ctrl UP
+ ctrl NEXT
+ ctrl PRIOR
+ ctrl HOME
+ ctrl END
+ ADD
+ SUBTRACT
+
+
+
+ 20
+
+
+ 20
+
+
+
+
+ -borderH
+ 10
+
+
+
+ 20
+
+
+
+
+
+
+ 0
+
+ treepathdisplay.node.*
+
+
+ cursor.text
+ 2,0
+
+ node.font.lineHeight + 4
+ center
+ cursor.text
+
+
+
+
+
+ -borderC
+ treecomboboxPopup
+
+ combobox.display.*
+ 50
+
+ 3
+
+ combobox
+
+
+ combobox
+
+
+
+
+ combobox.button.*
+ 18
+
+
+
+ -borderH
+ 2
+
+
+ none
+ 0
+ 0
+
+
+ 300
+
+
+
+ -borderG
+
+
+
+
+
+
+ 32767
+
+
+ fileselector.buttonOneLevelUp.*
+ 21
+
+
+ fileselector.buttonHome.*
+ 21
+
+
+ fileselector.buttonMRU.*
+ 20
+ Shows recently used folders
+
+
+ fileselector.buttonMRU.*
+ 20
+ Shows recently opened files
+
+
+ OK
+ 80
+
+
+ Cancel
+ 80
+
+
+ fileselector.buttonShowFolders.*
+ Shows folders in the file table
+ 20
+ 20
+
+
+ fileselector.buttonShowHidden.*
+ Shows hidden or system files
+ 20
+ 20
+
+
+ fileselector.buttonRefresh.*
+ Refreshes the file table
+ 20
+ 20
+
+
+ -borderC
+ 2
+
+
+ 70
+ 80
+ 140
+
+
+
+
+
+ BACK
+ RETURN
+
+
+
+ 20
+ 18
+
+
+
+ -borderH
+ 2
+
+ none
+ 0
+
+
+
+ -borderH
+ 2
+
+
+ none
+ 0
+
+
+ OK
+ 80
+
+
+ Cancel
+ 80
+
+
+
+
+
+ -borderC
+ background.border
+ 100
+ 100
+ 1
+ 1
+
+
+ black
+ 1.0
+
+
+
+
+
+
+ none
+
+ -
+ 0
+ 16
+
+
+ +
+ 0
+ 16
+
+
+ valueadjuster.display.*
+ center
+
+
+ 100
+ true
+
+
+
+
+ -borderE
+ -gradC
+ 2
+ 100
+
+
+
+
+
+
+
+
+ -borderC
+ 2
+ 30
+ 128
+ colorselector.*
+
+
+ -borderC
+ 2
+ 128
+ 128
+ colorselector.*
+
+
+ 32767
+
+
+ Current color:
+
+
+ -borderC
+ 2
+ 64
+ 64
+
+ white
+
+
+
+ Current color in ARGB hex format
+
+
+
+
+ frame.resizeable-title
+ background.border
+ 4
+ 6
+ -24
+ 20
+ true
+ -22
+ 6
+ false
+ -18
+ -18
+ white
+ 0
+ 0
+ 0
+ 0
+ arrow.*
+
+ left
+
+
+
+
+
+ frame.closebutton.*
+ 16
+ 14
+
+
+ frame.resizeIcon
+
+
+
+ -borderA
+ background.border
+ false
+ 4
+
+
+ true
+
+
+
+
+
+
+ popupmenu.button.*
+ popupmenu-button
+ 5,2
+
+
+
+ -borderA
+ background.border
+
+
+
+ popupmenu.separator
+
+
+ 0
+
+
+
+
+
+
+
+ 0
+ BOTTOMLEFT
+
+
+
+ CTRL TAB
+ CTRL SHIFT TAB
+
+
+
+
+
+
+
+
+
+
+
+ none
+
+ twl-logo
+ textarea.ul-bullet
+
+ 400
+
+
+ -borderC
+ background.border
+
+
+
+
+
+
+
+
+ -borderC
+ 2
+
+
+
+ none
+ 4
+
+
+ editfield.*
+ background.border
+
+ none
+ 0
+
+ topleft
+
+
+ 100
+ 100
+
+
+
+
+
+
+
+ image
+
+
+
+
+
+
+ Accept
+
+
+ Cancel
+
+
+
+ -borderA
+ 6
+
+
+
+
+ buttonBox.background
+ 3,2,1,2
+
+ 5
+ BOTTOM
+
+
+
+ -button-normal
+ background.border
+
+ -borderC
+ background.border
+
+
+
+ blockgame0
+ blockgame1
+ blockgame2
+ blockgame3
+ blockgame4
+ blockgame5
+ blockgame6
+ blockgame7
+
+
+
+
+
+
+
+
+
+ black
+ center
+
+
+
+ white
+ center
+
+
+
+
+
+
+
+ -borderC
+ background.border
+ 0
+ 150
+
+ none
+ 384
+ 400
+
+
+
+
+
+
+
+ -borderC
+ background.border
+
+ none
+ 10
+
+
+
+
diff --git a/newtheme/simpleGameMenu.xml b/newtheme/simpleGameMenu.xml
new file mode 100644
index 0000000..6893f06
--- /dev/null
+++ b/newtheme/simpleGameMenu.xml
@@ -0,0 +1,55 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ctrl A
+ ctrl X
+ ctrl C
+ ctrl V
+
+
+
+ none
+ none
+ normal
+ left
+ 0
+ 0
+ 0
+ 0
+ -defaultInputMap
+
+
+
+ button.*
+ background.border
+ center
+
+
+
+ chaos_sphere
+
+
+ white
+
+
+
\ No newline at end of file
diff --git a/pack.path b/pack.path
deleted file mode 100644
index 89b787f..0000000
--- a/pack.path
+++ /dev/null
@@ -1 +0,0 @@
-'/home/blendmaster/workspace/GuiAPI/twl/bin':'/home/blendmaster/workspace/GuiAPI/xpp3-1.1.4c':'/home/blendmaster/workspace/GuiAPI/theme':'/home/blendmaster/workspace/GuiAPI/bin'
diff --git a/readme.md b/readme.md
new file mode 100644
index 0000000..b30b83d
--- /dev/null
+++ b/readme.md
@@ -0,0 +1,31 @@
+GuiAPI
+======
+
+GuiAPI uses the TWL library from Matthias Mann, see http://twl.l33tlabs.org/
+
+Building
+--------
+
+* Use the forge installer script. (It will decompile minecraft for you, as well as download it, etc)
+* Now, download the Dev package you need for your version of GuiAPI. For example, if you were developing for 0.15.1, since there's no 0.15.1 dev package, you would download the dev package for 0.15.0. Copy the contents of this into Minecraft.jar.
+ * Dev Packages and all previous versions of GuiAPI are available in the 'releases' branch.
+* After that, drag in all the mcp source files from mcp/* into the src\minecraft directory in MCP.
+* Now recompile and test! You are all done.
+
+Packaging
+---------
+
+To create a distributable archive, package twl/bin/*, xpp*/*, theme/*, and bin/*.
+
+Credits
+-------
+
+- lahwran
+- ShaRose
+- _303
+- Lots of people who I forget. Open an issue or something if you helped...
+
+Documentation
+-------------
+
+All releases should be tagged here on github, and you can view the docs there. I haven't bothered setting up proper javadocs hosting on github yet.
diff --git a/release b/release
deleted file mode 100644
index e69de29..0000000
diff --git a/src/GuiModScreen.java b/src/GuiModScreen.java
deleted file mode 100644
index 6c28473..0000000
--- a/src/GuiModScreen.java
+++ /dev/null
@@ -1,281 +0,0 @@
-/**
- * GuiModScreen is the minecraft screen subclass that controls and renders TWL.
- * normally you will want to call it's static methods to use it, though
- * subclassing it and/or instantiating it
- * are also possible. however, to do so would use unsafe api (I still might
- * change things.)
- *
- * @author lahwran
- * @version 0.9.5
- * @see show
- */
-
-import net.minecraft.client.Minecraft;
-import de.matthiasmann.twl.Widget;
-import de.matthiasmann.twl.renderer.lwjgl.LWJGLRenderer;
-import de.matthiasmann.twl.renderer.lwjgl.RenderScale;
-
-public class GuiModScreen extends da {
- /**
- * reference to parent screen, is used to go back()
- * @see back()
- */
- public da parentScreen;
- public boolean drawbg = true;
- /**
- * actual main widget of this guiscreen
- */
- public Widget mainwidget;
-
- /*
- * public static int lastMouseX=0;
- * public static int lastMouseY=0;
- * public static boolean mousepressed[];
- *
- * static {
- * mousepressed=new boolean[Mouse.getButtonCount()];
- * for(int i=0;i8|
- Minecraft m = ModSettings.getMcinst();
- m.a(screen);
- screen.setActive();
- }
-
- /**
- * play a click sound. call after the user performs an action. already
- * called from setting widgets.
- */
- public static void clicksound()
- {
- Minecraft m = ModSettings.getMcinst();
- m.B.a("random.click", 1.0F, 1.0F);
-
- }
-
- /**
- * internal - actually sets the TWL screen.
- */
- private void setActive()
- {
- GuiWidgetScreen.getInstance().setScreen(mainwidget);
- }
-
- // protected void a(int x, int y, int button){}
- private int t = 0;
-
- //handleInput - is empty as this is where input is normally handled and we handle it elsewhere
- public void e(){}
-
- public void a(int j, int k, float f)
- {
- if (drawbg) i();// render default background
- // GL11.glClearColor(0.96f, 0.97f, 1.0f, 1.0f);
- // GL11.glClear(GL11.GL_COLOR_BUFFER_BIT);
-
- // mouse doesn't like to respond unless we do this
- /*
- * if(t==20)
- * {
- * System.out.println(j);
- * System.out.println(Mouse.getX());
- * System.out.println(k);
- * System.out.println(Mouse.getY());
- *
- * System.out.println(Mouse.isButtonDown(0));
- * t=0;
- * }
- * else
- * {
- * t++;
- * }
- */
- // LWJGLRenderer r = (LWJGLRenderer)
- // GuiWidgetScreen.getInstance().gui.getRenderer();
- // r.syncViewportSize();
- // GuiWidgetScreen.getInstance().layout();
- // for (int i=0; i E = new ArrayList
+ *
+ *
The single line editing (default mode) uses internal scrolling using
+ * the cursor position.
+ *
+ *
The multi line version uses internal horizontal scrolling, but for
+ * vertical scrolling it should be wrapped into a {@see ScrollPane}.
*
* @author Matthias Mann
*/
@@ -48,6 +62,7 @@ public class EditField extends Widget {
public static final StateKey STATE_ERROR = StateKey.get("error");
public static final StateKey STATE_READONLY = StateKey.get("readonly");
public static final StateKey STATE_HOVER = StateKey.get("hover");
+ public static final StateKey STATE_CURSOR_MOVED = StateKey.get("cursorMoved");
public interface Callback {
/**
@@ -62,12 +77,13 @@ public interface Callback {
public void callback(int key);
}
- final StringBuilder editBuffer;
- private final TextRenderer textRenderer;
+ final EditFieldModel editBuffer;
+ public TextRenderer textRenderer;
private PasswordMasker passwordMasking;
private Runnable modelChangeListener;
private StringModel model;
private boolean readOnly;
+ StringAttributes attributes;
private int cursorPos;
int scrollPos;
@@ -84,11 +100,13 @@ public interface Callback {
Image selectionImage;
private char passwordChar;
private Object errorMsg;
+ private boolean errorMsgFromModel;
private Callback[] callbacks;
private Menu popupMenu;
private boolean textLongerThenWidget;
private boolean forwardUnhandledKeysToCallback;
private boolean autoCompletionOnSetText = true;
+ boolean scrollToCursorOnSizeChange = true;
private EditFieldAutoCompletionWindow autoCompletionWindow;
private int autoCompletionHeight = 100;
@@ -104,12 +122,17 @@ public interface Callback {
* one as parent.
*
* @param parentAnimationState
+ * @param editFieldModel the edit field model to use
* @see AnimationState#AnimationState(de.matthiasmann.twl.AnimationState)
*/
- public EditField(AnimationState parentAnimationState) {
+ public EditField(AnimationState parentAnimationState, EditFieldModel editFieldModel) {
super(parentAnimationState, true);
-
- this.editBuffer = new StringBuilder();
+
+ if(editFieldModel == null) {
+ throw new NullPointerException("editFieldModel");
+ }
+
+ this.editBuffer = editFieldModel;
this.textRenderer = new TextRenderer(getAnimationState());
this.passwordChar = '*';
@@ -126,6 +149,20 @@ public EditField(AnimationState parentAnimationState) {
addActionMapping("selectAll", "selectAll");
}
+ /**
+ * Creates a new EditField with an optional parent animation state.
+ *
+ * Unlike other widgets which use the passed animation state directly,
+ * the EditField always creates it's animation state with the passed
+ * one as parent.
+ *
+ * @param parentAnimationState
+ * @see AnimationState#AnimationState(de.matthiasmann.twl.AnimationState)
+ */
+ public EditField(AnimationState parentAnimationState) {
+ this(parentAnimationState, new DefaultEditFieldModel());
+ }
+
public EditField() {
this(null);
}
@@ -168,6 +205,14 @@ public void setAutoCompletionOnSetText(boolean autoCompletionOnSetText) {
this.autoCompletionOnSetText = autoCompletionOnSetText;
}
+ public boolean isScrollToCursorOnSizeChange() {
+ return scrollToCursorOnSizeChange;
+ }
+
+ public void setScrollToCursorOnSizeChange(boolean scrollToCursorOnSizeChange) {
+ this.scrollToCursorOnSizeChange = scrollToCursorOnSizeChange;
+ }
+
protected void doCallback(int key) {
if(callbacks != null) {
for(Callback cb : callbacks) {
@@ -247,16 +292,13 @@ public StringModel getModel() {
}
public void setModel(StringModel model) {
+ removeModelChangeListener();
if(this.model != null) {
this.model.removeCallback(modelChangeListener);
}
this.model = model;
- if(this.model != null) {
- if(modelChangeListener == null) {
- modelChangeListener = new ModelChangeListener();
- }
- this.model.addCallback(modelChangeListener);
- modelChanged();
+ if(getGUI() != null) {
+ addModelChangeListener();
}
}
@@ -264,19 +306,26 @@ public void setModel(StringModel model) {
* Set a new text for this EditField.
* If the new text is longer then {@link #getMaxTextLength()} then it is truncated.
* The selection is cleared.
- * The cursor is positioned at the end of the new text.
+ * The cursor is positioned at the end of the new text (single line) or at
+ * the start of the text (multi line).
* If a model is set, then the model is also updated.
*
* @param text the new text
* @throws NullPointerException if text is null
+ * @see #setMultiLine(boolean)
*/
public void setText(String text) {
+ setText(text, false);
+ }
+
+ void setText(String text, boolean fromModel) {
text = TextUtil.limitStringLength(text, maxTextLength);
editBuffer.replace(0, editBuffer.length(), text);
- cursorPos = editBuffer.length();
+ cursorPos = multiLine ? 0 : editBuffer.length();
selectionStart = 0;
selectionEnd = 0;
- updateText(autoCompletionOnSetText, Event.KEY_NONE);
+ updateSelection();
+ updateText(autoCompletionOnSetText, fromModel, Event.KEY_NONE);
scrollToCursor(true);
}
@@ -284,6 +333,20 @@ public String getText() {
return editBuffer.toString();
}
+ public StringAttributes getStringAttributes() {
+ if(attributes == null) {
+ textRenderer.setCache(false);
+ attributes = new StringAttributes(editBuffer, getAnimationState());
+ }
+ return attributes;
+ }
+
+ public void disableStringAttributes() {
+ if(attributes != null) {
+ attributes = null;
+ }
+ }
+
public String getSelectedText() {
return editBuffer.substring(selectionStart, selectionEnd);
}
@@ -322,12 +385,14 @@ public void insertText(String str) {
}
int insertLength = Math.min(str.length(), maxTextLength - editBuffer.length());
if(insertLength > 0) {
- editBuffer.insert(cursorPos, str, 0, insertLength);
- cursorPos += insertLength;
- update = true;
+ int inserted = editBuffer.replace(cursorPos, 0, str.substring(0, insertLength));
+ if(inserted > 0) {
+ cursorPos += inserted;
+ update = true;
+ }
}
if(update) {
- updateText(true, Event.KEY_NONE);
+ updateText(true, false, Event.KEY_NONE);
}
}
}
@@ -363,7 +428,7 @@ public void cutToClipboard() {
text = getSelectedText();
if(!readOnly) {
deleteSelection();
- updateText(true, Event.KEY_DELETE);
+ updateText(true, false, Event.KEY_DELETE);
}
if(isPasswordMasking()) {
text = TextUtil.createString(passwordChar, text.length());
@@ -379,6 +444,34 @@ public void setMaxTextLength(int maxTextLength) {
this.maxTextLength = maxTextLength;
}
+ void removeModelChangeListener() {
+ if(model != null && modelChangeListener != null) {
+ model.removeCallback(modelChangeListener);
+ }
+ }
+
+ void addModelChangeListener() {
+ if(model != null) {
+ if(modelChangeListener == null) {
+ modelChangeListener = new ModelChangeListener();
+ }
+ model.addCallback(modelChangeListener);
+ modelChanged();
+ }
+ }
+
+ @Override
+ protected void afterAddToGUI(GUI gui) {
+ super.afterAddToGUI(gui);
+ addModelChangeListener();
+ }
+
+ @Override
+ protected void beforeRemoveFromGUI(GUI gui) {
+ removeModelChangeListener();
+ super.beforeRemoveFromGUI(gui);
+ }
+
@Override
protected void applyTheme(ThemeInfo themeInfo) {
super.applyTheme(themeInfo);
@@ -415,7 +508,17 @@ private void layoutInfoWindows() {
}
private void layoutAutocompletionWindow() {
- autoCompletionWindow.setPosition(getX(), getBottom());
+ int y = getBottom();
+ GUI gui = getGUI();
+ if(gui != null) {
+ if(y + autoCompletionHeight > gui.getInnerBottom()) {
+ int ytop = getY() - autoCompletionHeight;
+ if(ytop >= gui.getInnerY()) {
+ y = ytop;
+ }
+ }
+ }
+ autoCompletionWindow.setPosition(getX(), y);
autoCompletionWindow.setSize(getWidth(), autoCompletionHeight);
}
@@ -462,13 +565,11 @@ public int getPreferredInnerHeight() {
}
public void setErrorMessage(Object errorMsg) {
+ errorMsgFromModel = false;
getAnimationState().setAnimationState(STATE_ERROR, errorMsg != null);
if(this.errorMsg != errorMsg) {
this.errorMsg = errorMsg;
- GUI gui = getGUI();
- if(gui != null) {
- gui.requestToolTipUpdate(this);
- }
+ updateTooltip();
}
if(errorMsg != null) {
if(hasKeyboardFocus()) {
@@ -609,6 +710,8 @@ public boolean handleEvent(Event evt) {
return true;
}
break;
+ case Event.KEY_TAB:
+ return false;
default:
if(evt.hasKeyCharNoModifiers()) {
insertChar(evt.getKeyChar());
@@ -722,9 +825,19 @@ public void run() {
return menu;
}
- private void updateText(boolean updateAutoCompletion, int key) {
- if(model != null) {
- model.setValue(getText());
+ private void updateText(boolean updateAutoCompletion, boolean fromModel, int key) {
+ if(model != null && !fromModel) {
+ try {
+ model.setValue(getText());
+ if(errorMsgFromModel) {
+ setErrorMessage(null);
+ }
+ } catch(Exception ex) {
+ if(errorMsg == null || errorMsgFromModel) {
+ setErrorMessage(ex.getMessage());
+ errorMsgFromModel = true;
+ }
+ }
}
updateTextDisplay();
if(multiLine) {
@@ -742,6 +855,7 @@ private void updateText(boolean updateAutoCompletion, int key) {
private void updateTextDisplay() {
textRenderer.setCharSequence(passwordMasking != null ? passwordMasking : editBuffer);
+ textRenderer.cacheDirty = true;
checkTextWidth();
scrollToCursor(false);
}
@@ -775,8 +889,12 @@ protected void moveCursorY(int dir, boolean select) {
protected void setCursorPos(int pos, boolean select) {
pos = Math.max(0, Math.min(editBuffer.length(), pos));
if(!select) {
+ boolean hadSelection = hasSelection();
selectionStart = pos;
selectionEnd = pos;
+ if(hadSelection) {
+ updateSelection();
+ }
}
if(this.cursorPos != pos) {
if(select) {
@@ -795,13 +913,27 @@ protected void setCursorPos(int pos, boolean select) {
selectionStart = selectionEnd;
selectionEnd = t;
}
+ updateSelection();
}
+ if(this.cursorPos != pos) {
+ getAnimationState().resetAnimationTime(STATE_CURSOR_MOVED);
+ }
this.cursorPos = pos;
scrollToCursor(false);
updateAutoCompletion();
}
}
+
+ protected void updateSelection() {
+ if(attributes != null) {
+ attributes.removeAnimationState(TextWidget.STATE_TEXT_SELECTION);
+ attributes.setAnimationState(TextWidget.STATE_TEXT_SELECTION,
+ selectionStart, selectionEnd, true);
+ attributes.optimize();
+ textRenderer.cacheDirty = true;
+ }
+ }
public void setCursorPos(int pos) {
if(pos < 0 || pos > editBuffer.length()) {
@@ -813,6 +945,7 @@ public void setCursorPos(int pos) {
public void selectAll() {
selectionStart = 0;
selectionEnd = editBuffer.length();
+ updateSelection();
}
public void setSelection(int start, int end) {
@@ -821,6 +954,7 @@ public void setSelection(int start, int end) {
}
selectionStart = start;
selectionEnd = end;
+ updateSelection();
}
protected void selectWordFromMouse(int index) {
@@ -832,6 +966,7 @@ protected void selectWordFromMouse(int index) {
while(selectionEnd < editBuffer.length() && !Character.isWhitespace(editBuffer.charAt(selectionEnd))) {
selectionEnd++;
}
+ updateSelection();
}
protected void scrollToCursor(boolean force) {
@@ -868,12 +1003,13 @@ protected void insertChar(char ch) {
update = true;
}
if(editBuffer.length() < maxTextLength) {
- editBuffer.insert(cursorPos, ch);
- cursorPos++;
- update = true;
+ if(editBuffer.replace(cursorPos, 0, ch)) {
+ cursorPos++;
+ update = true;
+ }
}
if(update) {
- updateText(true, Event.KEY_NONE);
+ updateText(true, false, Event.KEY_NONE);
}
}
}
@@ -882,7 +1018,7 @@ protected void deletePrev() {
if(!readOnly) {
if(hasSelection()) {
deleteSelection();
- updateText(true, Event.KEY_DELETE);
+ updateText(true, false, Event.KEY_DELETE);
} else if(cursorPos > 0) {
--cursorPos;
deleteNext();
@@ -894,24 +1030,25 @@ protected void deleteNext() {
if(!readOnly) {
if(hasSelection()) {
deleteSelection();
- updateText(true, Event.KEY_DELETE);
+ updateText(true, false, Event.KEY_DELETE);
} else if(cursorPos < editBuffer.length()) {
- editBuffer.deleteCharAt(cursorPos);
- updateText(true, Event.KEY_DELETE);
+ if(editBuffer.replace(cursorPos, 1, "") >= 0) {
+ updateText(true, false, Event.KEY_DELETE);
+ }
}
}
}
protected void deleteSelection() {
- editBuffer.delete(selectionStart, selectionEnd);
- selectionEnd = selectionStart;
- setCursorPos(selectionStart, false);
+ if(editBuffer.replace(selectionStart, selectionEnd-selectionStart, "") >= 0) {
+ setCursorPos(selectionStart, false);
+ }
}
protected void modelChanged() {
String modelText = model.getValue();
if(editBuffer.length() != modelText.length() || !getText().equals(modelText)) {
- setText(modelText);
+ setText(modelText, true);
}
}
@@ -932,7 +1069,7 @@ protected int getLineHeight() {
}
protected int computeLineNumber(int cursorPos) {
- final StringBuilder eb = this.editBuffer;
+ final EditFieldModel eb = this.editBuffer;
int lineNr = 0;
for(int i=0 ; i 0 && eb.charAt(cursorPos-1) != '\n') {
cursorPos--;
}
@@ -954,7 +1091,7 @@ protected int computeLineStart(int cursorPos) {
}
protected int computeLineEnd(int cursorPos) {
- final StringBuilder eb = this.editBuffer;
+ final EditFieldModel eb = this.editBuffer;
int endIndex = eb.length();
if(!multiLine) {
return endIndex;
@@ -1047,8 +1184,22 @@ private void openErrorInfoWindow() {
}
private void layoutErrorInfoWindow() {
- errorInfoWindow.setSize(getWidth(), errorInfoWindow.getPreferredHeight());
- errorInfoWindow.setPosition(getX(), getBottom());
+ int x = getX();
+ int width = getWidth();
+
+ Widget container = errorInfoWindow.getParent();
+ if(container != null) {
+ width = Math.max(width, computeSize(
+ errorInfoWindow.getMinWidth(),
+ errorInfoWindow.getPreferredWidth(),
+ errorInfoWindow.getMaxWidth()));
+ int popupMaxRight = container.getInnerRight();
+ if(x + width > popupMaxRight) {
+ x = popupMaxRight - Math.min(width, container.getInnerWidth());
+ }
+ errorInfoWindow.setSize(width, errorInfoWindow.getPreferredHeight());
+ errorInfoWindow.setPosition(x, getBottom());
+ }
}
@Override
@@ -1086,6 +1237,8 @@ public void run() {
protected class TextRenderer extends TextWidget {
int lastTextX;
int lastScrollPos;
+ AttributedStringFontCache cache;
+ boolean cacheDirty;
protected TextRenderer(AnimationState animState) {
super(animState);
@@ -1098,7 +1251,10 @@ protected void paintWidget(GUI gui) {
}
lastScrollPos = hasFocusOrPopup() ? scrollPos : 0;
lastTextX = computeTextX();
- if(hasSelection() && hasFocusOrPopup()) {
+ Font font = getFont();
+ if(attributes != null && font instanceof Font2) {
+ paintWithAttributes((Font2)font);
+ } else if(hasSelection() && hasFocusOrPopup()) {
if(multiLine) {
paintMultiLineWithSelection();
} else {
@@ -1112,17 +1268,19 @@ protected void paintWidget(GUI gui) {
protected void paintWithSelection(int lineStart, int lineEnd, int yoff) {
int selStart = selectionStart;
int selEnd = selectionEnd;
- if(selectionImage != null && selEnd > lineStart && selStart < lineEnd) {
+ if(selectionImage != null && selEnd > lineStart && selStart <= lineEnd) {
int xpos0 = lastTextX + computeRelativeCursorPositionX(lineStart, selStart);
- int xpos1 = lastTextX + computeRelativeCursorPositionX(lineStart, Math.min(lineEnd, selEnd));
+ int xpos1 = (lineEnd < selEnd) ? getInnerRight() :
+ lastTextX + computeRelativeCursorPositionX(lineStart, Math.min(lineEnd, selEnd));
selectionImage.draw(getAnimationState(), xpos0, yoff,
xpos1 - xpos0, getFont().getLineHeight());
}
+
paintWithSelection(getAnimationState(), selStart, selEnd, lineStart, lineEnd, yoff);
}
protected void paintMultiLineWithSelection() {
- final StringBuilder eb = editBuffer;
+ final EditFieldModel eb = editBuffer;
int lineStart = 0;
int endIndex = eb.length();
int yoff = computeTextY();
@@ -1136,15 +1294,78 @@ protected void paintMultiLineWithSelection() {
lineStart = lineEnd + 1;
}
}
+
+ protected void paintMultiLineSelectionBackground() {
+ int lineHeight = getLineHeight();
+ int lineStart = computeLineStart(selectionStart);
+ int lineNumber = computeLineNumber(lineStart);
+ int endIndex = selectionEnd;
+ int yoff = computeTextY() + lineHeight * lineNumber;
+ int xstart = lastTextX + computeRelativeCursorPositionX(lineStart, selectionStart);
+ while(lineStart < endIndex) {
+ int lineEnd = computeLineEnd(lineStart);
+ int xend;
+
+ if(lineEnd < endIndex) {
+ xend = getInnerRight();
+ } else {
+ xend = lastTextX + computeRelativeCursorPositionX(lineStart, endIndex);
+ }
+
+ selectionImage.draw(getAnimationState(), xstart, yoff, xend - xstart, lineHeight);
+
+ yoff += lineHeight;
+ lineStart = lineEnd + 1;
+ xstart = getInnerX();
+ }
+ }
+
+ protected void paintWithAttributes(Font2 font) {
+ if(selectionEnd > selectionStart && selectionImage != null) {
+ paintMultiLineSelectionBackground();
+ }
+ if(cache == null || cacheDirty) {
+ cacheDirty = false;
+ if(multiLine) {
+ cache = font.cacheMultiLineText(cache, attributes);
+ } else {
+ cache = font.cacheText(cache, attributes);
+ }
+ }
+ int y = computeTextY();
+ if(cache != null) {
+ cache.draw(lastTextX, y);
+ } else if(multiLine) {
+ font.drawMultiLineText(lastTextX, y, attributes);
+ } else {
+ font.drawText(lastTextX, y, attributes);
+ }
+ }
@Override
protected void sizeChanged() {
- scrollToCursor(true);
+ if(scrollToCursorOnSizeChange) {
+ scrollToCursor(true);
+ }
}
@Override
protected int computeTextX() {
- return getInnerX() - lastScrollPos;
+ int x = getInnerX();
+ int pos = getAlignment().hpos;
+ if(pos > 0) {
+ x += Math.max(0, getInnerWidth() - computeTextWidth()) * pos / 2;
+ }
+ return x - lastScrollPos;
+ }
+
+ @Override
+ public void destroy() {
+ super.destroy();
+ if(cache != null) {
+ cache.destroy();
+ cache = null;
+ }
}
}
diff --git a/twl/src/de/matthiasmann/twl/Event.java b/twl/src/de/matthiasmann/twl/Event.java
index cf0e3c4..2c73235 100644
--- a/twl/src/de/matthiasmann/twl/Event.java
+++ b/twl/src/de/matthiasmann/twl/Event.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2008-2010, Matthias Mann
+ * Copyright (c) 2008-2012, Matthias Mann
*
* All rights reserved.
*
@@ -86,10 +86,16 @@ public enum Type {
MOUSE_WHEEL(true, false),
/**
* A key has been pressed. Not all keys generate characters.
+ * @see #isKeyEvent()
+ * @see #isKeyPressedEvent()
+ * @see #isKeyRepeated()
+ * @see #hasKeyChar()
+ * @see #hasKeyCharNoModifiers()
*/
KEY_PRESSED(false, true),
/**
* A key has been released. No character data is available.
+ * @see #isKeyEvent()
*/
KEY_RELEASED(false, true),
/**
@@ -376,16 +382,16 @@ public final boolean isMouseDragEnd() {
}
/**
- * Returns the current mouse X coordinate
- * @return the current mouse X coordinate
+ * Returns the current absolute mouse X coordinate
+ * @return the current absolute mouse X coordinate
*/
public final int getMouseX() {
return mouseX;
}
/**
- * Returns the current mouse Y coordinate
- * @return the current mouse Y coordinate
+ * Returns the current absolute mouse Y coordinate
+ * @return the current absolute mouse Y coordinate
*/
public final int getMouseY() {
return mouseY;
@@ -495,6 +501,13 @@ final Event createSubEvent(Type newType) {
return subEvent;
}
+ final Event createSubEvent(int x, int y) {
+ Event e = createSubEvent(type);
+ e.mouseX = x;
+ e.mouseY = y;
+ return e;
+ }
+
void setModifier(int mask, boolean pressed) {
if(pressed) {
modifier |= mask;
diff --git a/twl/src/de/matthiasmann/twl/FPSCounter.java b/twl/src/de/matthiasmann/twl/FPSCounter.java
index ad130a5..789f2fe 100644
--- a/twl/src/de/matthiasmann/twl/FPSCounter.java
+++ b/twl/src/de/matthiasmann/twl/FPSCounter.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2008-2010, Matthias Mann
+ * Copyright (c) 2008-2012, Matthias Mann
*
* All rights reserved.
*
@@ -31,9 +31,14 @@
/**
* A simple FPS counter.
- * Measures the time required to render a specified number of
- * frames (default 100) using System.nanoTime.
- *
+ *
+ *
Measures the time required to render a specified number of
+ * frames (default 100) using System.nanoTime and displays the
+ * frame rate:
{@code 1e9 * framesToCount / elapsedNanoseconds }
+ *
+ *
This widget does not generate garbage while measuring and
+ * displaying the frame rate.
+ *
* @see System#nanoTime()
* @author Matthias Mann
*/
@@ -43,7 +48,7 @@ public class FPSCounter extends Label {
private int frames;
private int framesToCount = 100;
- private final char[] fmtBuffer;
+ private final StringBuilder fmtBuffer;
private final int decimalPoint;
private final long scale;
@@ -60,13 +65,14 @@ public FPSCounter(int numIntegerDigits, int numDecimalDigits) {
if(numDecimalDigits < 0) {
throw new IllegalArgumentException("numDecimalDigits must be >= 0");
}
- decimalPoint = numDecimalDigits;
+ decimalPoint = numIntegerDigits + 1;
startTime = System.nanoTime();
- fmtBuffer = new char[numIntegerDigits + numDecimalDigits + Integer.signum(numDecimalDigits)];
+ fmtBuffer = new StringBuilder();
+ fmtBuffer.setLength(numIntegerDigits + numDecimalDigits + Integer.signum(numDecimalDigits));
// compute the scale based on the number of decimal places
long tmp = (long)1e9;
- for(int i=0 ; i 0) {
- buf[--pos] = (char)('0' + (value % 10));
- value /= 10;
- if(--decimalPoint == 0) {
- buf[--pos] = '.';
- }
- }
- if(value > 0) {
- // when thw frame rate is too high, then we display "999.99"
- for(int i=0 ; i> 1)) / elapsed));
frames = 0;
}
- private void updateText(int scaledValue) {
- format(fmtBuffer, scaledValue, decimalPoint);
- setText(new String(fmtBuffer));
+ private void updateText(int value) {
+ StringBuilder buf = fmtBuffer;
+ int pos = buf.length();
+ do {
+ buf.setCharAt(--pos, (char)('0' + (value % 10)));
+ value /= 10;
+ if(decimalPoint == pos) {
+ buf.setCharAt(--pos, '.');
+ }
+ } while(pos > 0);
+ if(value > 0) {
+ // when the frame rate is too high, then we display "999.99"
+ pos = buf.length();
+ do {
+ buf.setCharAt(--pos, '9');
+ if(decimalPoint == pos) {
+ --pos;
+ }
+ } while(pos > 0);
+ }
+ setCharSequence(buf);
}
}
diff --git a/twl/src/de/matthiasmann/twl/FileSelector.java b/twl/src/de/matthiasmann/twl/FileSelector.java
index 0e8b232..8dd6475 100644
--- a/twl/src/de/matthiasmann/twl/FileSelector.java
+++ b/twl/src/de/matthiasmann/twl/FileSelector.java
@@ -368,7 +368,7 @@ public void setFileSystemModel(FileSystemModel fsm) {
currentFolder.setSeparator(fsm.getSeparator());
autoCompletion.setDataSource(new FileSystemAutoCompletionDataSource(fsm,
FileSystemTreeModel.FolderFilter.instance));
- if(folderMRU.getNumEntries() == 0 || !gotoFolderFromMRU(0) || !goHome()) {
+ if(!gotoFolderFromMRU(0) && !goHome()) {
setCurrentNode(model);
}
}
@@ -405,6 +405,23 @@ public void setAllowFolderSelection(boolean allowFolderSelection) {
this.allowFolderSelection = allowFolderSelection;
selectionChanged();
}
+
+ public boolean getAllowHorizontalScrolling() {
+ return fileTableSP.getFixed() != ScrollPane.Fixed.HORIZONTAL;
+ }
+
+ /**
+ * Controls if the file table allows horizontal scrolling or not.
+ *
+ * Default is true.
+ *
+ * @param allowHorizontalScrolling true if horizontal scrolling is allowed
+ */
+ public void setAllowHorizontalScrolling(boolean allowHorizontalScrolling) {
+ fileTableSP.setFixed(allowHorizontalScrolling
+ ? ScrollPane.Fixed.NONE
+ : ScrollPane.Fixed.HORIZONTAL);
+ }
public void addCallback(Callback callback) {
callbacks = CallbackSupport.addCallbackToList(callbacks, callback, Callback.class);
@@ -468,6 +485,10 @@ public boolean selectFile(Object file) {
}
return false;
}
+
+ public void clearSelection() {
+ fileTable.clearSelection();
+ }
/**
* Adds a named file filter to the FileSelector.
@@ -759,6 +780,9 @@ private void addToMRU(FileTable.Entry[] selection) {
}
boolean gotoFolderFromMRU(int idx) {
+ if(idx >= folderMRU.getNumEntries()) {
+ return false;
+ }
String path = folderMRU.getEntry(idx);
try {
TreeTableNode node = resolvePath(path);
diff --git a/twl/src/de/matthiasmann/twl/FileTable.java b/twl/src/de/matthiasmann/twl/FileTable.java
index 125e0ff..9634d52 100644
--- a/twl/src/de/matthiasmann/twl/FileTable.java
+++ b/twl/src/de/matthiasmann/twl/FileTable.java
@@ -161,6 +161,10 @@ public boolean setSelection(Object file) {
}
return false;
}
+
+ public void clearSelection() {
+ fileTableSelectionModel.clearSelection();
+ }
public void setSortColumn(SortColumn column) {
if(column == null) {
diff --git a/twl/src/de/matthiasmann/twl/GUI.java b/twl/src/de/matthiasmann/twl/GUI.java
index 59517e1..4ddff9f 100644
--- a/twl/src/de/matthiasmann/twl/GUI.java
+++ b/twl/src/de/matthiasmann/twl/GUI.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2008-2010, Matthias Mann
+ * Copyright (c) 2008-2012, Matthias Mann
*
* All rights reserved.
*
@@ -111,6 +111,7 @@ public interface AsyncCompletionListener {
private int mouseClickedX;
private int mouseClickedY;
private long mouseEventTime;
+ private long tooltipEventTime;
private long mouseClickedTime;
private long keyEventTime;
private int keyRepeatDelay;
@@ -119,14 +120,12 @@ public interface AsyncCompletionListener {
private Widget lastMouseClickWidget;
private PopupWindow boundDragPopup;
private Runnable boundDragCallback;
+ private Widget focusKeyWidget;
private int mouseIdleTime = 60;
private boolean mouseIdleState;
private MouseIdleListener mouseIdleListener;
- private Rect[] clipRects;
- private int numClipRects;
-
private InfoWindow activeInfoWindow;
private final Widget infoWindowPlaceholder;
@@ -137,9 +136,13 @@ public interface AsyncCompletionListener {
private long tooltipClosedTime;
final ArrayList activeTimers;
- private final ArrayList invokeLaterQueue;
final ExecutorService executorService;
+ private final Object invokeLock;
+ private Runnable[] invokeLaterQueue;
+ private int invokeLaterQueueSize;
+ private Runnable[] invokeRunnables;
+
/**
* Constructs a new GUI manager with the given renderer and a default root
* pane.
@@ -189,7 +192,6 @@ public GUI(Widget rootPane, Renderer renderer, Input input) {
this.event = new Event();
this.rootPane = rootPane;
this.rootPane.setFocusKeyEnabled(false);
- this.clipRects = new Rect[8];
this.infoWindowPlaceholder = new Widget();
this.infoWindowPlaceholder.setTheme("");
@@ -199,8 +201,10 @@ public GUI(Widget rootPane, Renderer renderer, Input input) {
this.tooltipWindow.setVisible(false);
this.activeTimers = new ArrayList();
- this.invokeLaterQueue = new ArrayList();
this.executorService = Executors.newSingleThreadExecutor(new TF()); // thread creatation is lazy
+ this.invokeLock = new Object();
+ this.invokeLaterQueue = new Runnable[16];
+ this.invokeRunnables = new Runnable[16];
setTheme("");
setFocusKeyEnabled(false);
@@ -301,8 +305,11 @@ public void invokeLater(Runnable runnable) {
if(runnable == null) {
throw new IllegalArgumentException("runnable is null");
}
- synchronized(invokeLaterQueue) {
- invokeLaterQueue.add(runnable);
+ synchronized(invokeLock) {
+ if(invokeLaterQueueSize == invokeLaterQueue.length) {
+ growInvokeLaterQueue();
+ }
+ invokeLaterQueue[invokeLaterQueueSize++] = runnable;
}
}
@@ -368,12 +375,6 @@ public boolean requestToolTip(Widget widget, int x, int y,
return false;
}
- public void requestToolTipUpdate(Widget widget) {
- if(tooltipOwner == widget) {
- tooltipOwner = null;
- }
- }
-
public MouseIdleListener getMouseIdleListener() {
return mouseIdleListener;
}
@@ -439,9 +440,32 @@ public void setTooltipOffset(int tooltipOffsetX, int tooltipOffsetY) {
this.tooltipOffsetX = tooltipOffsetX;
this.tooltipOffsetY = tooltipOffsetY;
}
+
+ /**
+ * Sets set offscreen rendering delegate on the tooltip window.
+ * Can be null to disable offscreen rendering.
+ *
+ * @param renderOffscreen the offscreen rendering delegate.
+ * @see Widget#setRenderOffscreen(de.matthiasmann.twl.Widget.RenderOffscreen)
+ */
+ public void setTooltipWindowRenderOffscreen(RenderOffscreen renderOffscreen) {
+ tooltipWindow.setRenderOffscreen(renderOffscreen);
+ }
+
+ /**
+ * Changes the theme name of the tooltip window and applies and calls {@link #reapplyTheme() }
+ *
+ * @param theme the new theme path element
+ * @see Widget#setTheme(java.lang.String)
+ */
+ public void setTooltipWindowTheme(String theme) {
+ tooltipWindow.setTheme(theme);
+ tooltipWindow.reapplyTheme();
+ }
/**
* Throws UnsupportedOperationException
+ * @throws UnsupportedOperationException always
*/
@Override
public boolean setPosition(int x, int y) {
@@ -450,6 +474,7 @@ public boolean setPosition(int x, int y) {
/**
* Throws UnsupportedOperationException
+ * @throws UnsupportedOperationException always
*/
@Override
public void insertChild(Widget child, int index) {
@@ -458,6 +483,7 @@ public void insertChild(Widget child, int index) {
/**
* Throws UnsupportedOperationException
+ * @throws UnsupportedOperationException always
*/
@Override
public void removeAllChildren() {
@@ -466,6 +492,7 @@ public void removeAllChildren() {
/**
* Throws UnsupportedOperationException
+ * @throws UnsupportedOperationException always
*/
@Override
public Widget removeChild(int index) {
@@ -524,7 +551,10 @@ public void setSize() {
*
{@link #setCursor() }
*
*
- * This is the easiest method to use this GUI
+ * This is the easiest method to use this GUI.
+ *
+ *
When not using this method care must be taken to invoke the methods
+ * in the right order. See the javadoc of the individual methods for details.
*/
public void update() {
setSize();
@@ -553,6 +583,10 @@ public void resyncTimerAfterPause() {
* Updates the current time returned by {@code getCurrentTime} by calling
* {@link Renderer#getTimeMillis() } and computes the delta time since the last update.
*
+ *
This must be called exactly once per frame and befiore processing
+ * input events or calling {@link #updateTimers() }. See {@link #update() }
+ * for the sequence in which the methods of this class should be called.
+ *
* @see #getCurrentTime()
* @see #getTimeMillis()
*/
@@ -564,7 +598,9 @@ public void updateTime() {
/**
* Updates all active timers with the delta time computed by {@code updateTime}.
- * This method must be called exactly once after a call to {@code updateTime}.
+ *
+ *
This method must be called exactly once after a call to {@code updateTime}.
+ *
* @see #updateTime()
*/
public void updateTimers() {
@@ -584,37 +620,43 @@ public void updateTimers() {
*/
public void invokeRunables() {
Runnable[] runnables = null;
- synchronized(invokeLaterQueue) {
- int size = invokeLaterQueue.size();
- if(size > 0) {
- runnables = invokeLaterQueue.toArray(new Runnable[size]);
- invokeLaterQueue.clear();
+ int count;
+ synchronized(invokeLock) {
+ count = invokeLaterQueueSize;
+ if(count > 0) {
+ invokeLaterQueueSize = 0;
+ runnables = invokeLaterQueue;
+ invokeLaterQueue = invokeRunnables;
+ invokeRunnables = runnables;
}
}
- if(runnables != null) {
- for(Runnable r : runnables) {
- try {
- r.run();
- } catch (Throwable ex) {
- Logger.getLogger(GUI.class.getName()).log(Level.SEVERE, "Exception in runnable", ex);
- }
+ for(int i=0 ; iIf the widget is disabled or did not define a cursor then
+ * it's parent widget is tried. If no cursor was found the default
+ * OS cursor will be displayed.
+ *
* @see Renderer#setCursor(de.matthiasmann.twl.renderer.MouseCursor)
+ * @see Widget#getMouseCursor(de.matthiasmann.twl.Event)
*/
public void setCursor() {
+ event.type = Event.Type.MOUSE_MOVED;
Widget widget = getWidgetUnderMouse();
- if(widget != null && widget.isEnabled()) {
- MouseCursor cursor = widget.getMouseCursor();
- renderer.setCursor(cursor);
+ MouseCursor cursor = null;
+ while(widget != null) {
+ if(widget.isEnabled()) {
+ cursor = widget.getMouseCursor(event);
+ if(cursor != null) {
+ break;
+ }
+ }
+ widget = widget.getParent();
}
+ renderer.setCursor(cursor);
}
/**
- * Polls input by calling {@link Input#pollInput(de.matthiasmann.twl.GUI) } if an input source was specified.
- * If {@code pollInput} returned false then {@link #clearKeyboardState() } and {@link #clearMouseState() } are called.
+ * Polls input by calling {@link Input#pollInput(de.matthiasmann.twl.GUI) }
+ * if an input source was specified, otherwise it does nothing.
+ *
+ *
If {@code pollInput} returned false then {@link #clearKeyboardState() }
+ * and {@link #clearMouseState() } are called.
+ *
+ *
If you don't want to use polled input you can easily use a push model
+ * for handling input. Just call the following methods:
+ *
{@link #handleKey(int, char, boolean) } for every keyboard event
+ *
{@link #handleMouse(int, int, int, boolean) } for every mouse event (buttons or move)
+ *
{@link #handleMouseWheel(int) } for any mouse wheel event
+ *
These metods (including this one) needs to be called after {@link #updateTime() }
*/
public void handleInput() {
if(input != null && !input.pollInput(this)) {
@@ -656,6 +721,7 @@ public void handleInput() {
*/
public final boolean handleMouse(int mouseX, int mouseY, int button, boolean pressed) {
mouseEventTime = curTime;
+ tooltipEventTime = curTime;
event.mouseButton = button;
// only the previously pressed mouse button
@@ -885,10 +951,10 @@ public final boolean handleKey(int keyCode, char keyChar, boolean pressed) {
if(pressed) {
keyRepeatDelay = KEYREPEAT_INITIAL_DELAY;
- return sendEvent(Event.Type.KEY_PRESSED);
+ return sendKeyEvent(Event.Type.KEY_PRESSED);
} else {
keyRepeatDelay = NO_REPEAT;
- return sendEvent(Event.Type.KEY_RELEASED);
+ return sendKeyEvent(Event.Type.KEY_RELEASED);
}
} else {
keyRepeatDelay = NO_REPEAT;
@@ -923,7 +989,7 @@ public final void handleKeyRepeat() {
keyEventTime = curTime;
keyRepeatDelay = KEYREPEAT_INTERVAL_DELAY;
event.keyRepeated = true;
- sendEvent(Event.Type.KEY_PRESSED); // refire last key event
+ sendKeyEvent(Event.Type.KEY_PRESSED); // refire last key event
}
}
}
@@ -941,7 +1007,7 @@ public final void handleTooltips() {
Widget widgetUnderMouse = getWidgetUnderMouse();
if(widgetUnderMouse != tooltipOwner) {
if(widgetUnderMouse != null && (
- ((curTime-mouseEventTime) > tooltipDelay) ||
+ ((curTime-tooltipEventTime) > tooltipDelay) ||
(hadOpenTooltip && (curTime-tooltipClosedTime) < tooltipReappearDelay))) {
setTooltip(
event.mouseX + tooltipOffsetX,
@@ -981,7 +1047,7 @@ private Widget sendMouseEvent(Event.Type type, Widget target) {
if(target != null) {
if(target.isEnabled() || !isMouseAction(event)) {
- target.handleEvent(event);
+ target.handleEvent(target.translateMouseEvent(event));
}
return target;
} else {
@@ -1000,12 +1066,32 @@ private Widget sendMouseEvent(Event.Type type, Widget target) {
}
}
- private boolean sendEvent(Event.Type type) {
- assert !type.isMouseEvent;
+ private static final int FOCUS_KEY = Event.KEY_TAB;
+
+ boolean isFocusKey() {
+ return event.keyCode == FOCUS_KEY &&
+ ((event.modifier & (Event.MODIFIER_CTRL|Event.MODIFIER_META|Event.MODIFIER_ALT)) == 0);
+ }
+
+ void setFocusKeyWidget(Widget widget) {
+ if(focusKeyWidget == null && isFocusKey()) {
+ focusKeyWidget = widget;
+ }
+ }
+
+ private boolean sendKeyEvent(Event.Type type) {
+ assert type.isKeyEvent;
popupEventOccured = false;
+ focusKeyWidget = null;
event.type = type;
event.dragEvent = false;
- return getTopPane().handleEvent(event);
+ boolean handled = getTopPane().handleEvent(event);
+ if(!handled && focusKeyWidget != null) {
+ focusKeyWidget.handleFocusKeyEvent(event);
+ handled = true;
+ }
+ focusKeyWidget = null; // allow GC
+ return handled;
}
private void sendPopupEvent(Event.Type type) {
@@ -1056,6 +1142,7 @@ void closePopup(PopupWindow popup) {
popupEventOccured = true;
closeInfoFromWidget(popup);
requestKeyboardFocus(getTopPane());
+ resendLastMouseMove();
}
boolean hasOpenPopups(Widget owner) {
@@ -1162,6 +1249,17 @@ protected boolean requestKeyboardFocus(Widget child) {
return super.requestKeyboardFocus(child);
}
+ void requestTooltipUpdate(Widget widget, boolean resetToolTipTimer) {
+ if(tooltipOwner == widget) {
+ tooltipOwner = null;
+ if(resetToolTipTimer) {
+ hideTooltip();
+ hadOpenTooltip = false;
+ tooltipEventTime = curTime;
+ }
+ }
+ }
+
private void hideTooltip() {
if(tooltipWindow.isVisible()) {
tooltipClosedTime = curTime;
@@ -1195,7 +1293,6 @@ private void setTooltip(int x, int y, Widget widget, Object content,
}
tooltipLabel.setBackground(null);
tooltipLabel.setText(text);
- tooltipWindow.adjustSize();
} else if(content instanceof Widget) {
Widget tooltipWidget = (Widget)content;
if(tooltipWidget.getParent() != null && tooltipWidget.getParent() != tooltipWindow) {
@@ -1203,11 +1300,18 @@ private void setTooltip(int x, int y, Widget widget, Object content,
}
tooltipWindow.removeAllChildren();
tooltipWindow.add(tooltipWidget);
- tooltipWindow.adjustSize();
} else {
throw new IllegalArgumentException("Unsupported data type");
}
+ tooltipWindow.adjustSize();
+
+ // some Widgets (esp TextArea) have complex sizing policy
+ // give them a 2nd chance
+ if(tooltipWindow.isLayoutInvalid()) {
+ tooltipWindow.adjustSize();
+ }
+
int ttWidth = tooltipWindow.getWidth();
int ttHeight = tooltipWindow.getHeight();
@@ -1254,38 +1358,6 @@ private void setTooltip(int x, int y, Widget widget, Object content,
tooltipWindow.setPosition(x, y);
tooltipWindow.setVisible(true);
}
-
- void clipEnter(int x, int y, int w, int h) {
- Rect rect;
- if(numClipRects == clipRects.length) {
- Rect[] newRects = new Rect[numClipRects*2];
- System.arraycopy(clipRects, 0, newRects, 0, numClipRects);
- clipRects = newRects;
- }
- if((rect = clipRects[numClipRects]) == null) {
- rect = new Rect();
- clipRects[numClipRects] = rect;
- }
- rect.setXYWH(x, y, w, h);
- if(numClipRects > 0) {
- rect.intersect(clipRects[numClipRects-1]);
- }
- renderer.setClipRect(rect);
- numClipRects++;
- }
-
- boolean clipEmpty() {
- return clipRects[numClipRects-1].isEmpty();
- }
-
- void clipLeave() {
- numClipRects--;
- if(numClipRects == 0) {
- renderer.setClipRect(null);
- } else {
- renderer.setClipRect(clipRects[numClipRects-1]);
- }
- }
private void callMouseIdleListener() {
if(mouseIdleListener != null) {
@@ -1296,8 +1368,14 @@ private void callMouseIdleListener() {
}
}
}
+
+ private void growInvokeLaterQueue() {
+ Runnable[] tmp = new Runnable[invokeLaterQueueSize*2];
+ System.arraycopy(invokeLaterQueue, 0, tmp, 0, invokeLaterQueueSize);
+ invokeLaterQueue = tmp;
+ }
- static class TooltipWindow extends Widget {
+ static class TooltipWindow extends Container {
public static final StateKey STATE_FADE = StateKey.get("fade");
private int fadeInTime;
@@ -1328,31 +1406,6 @@ protected void paint(GUI gui) {
super.paint(gui);
}
}
-
- @Override
- public int getMinWidth() {
- return BoxLayout.computeMinWidthVertical(this);
- }
-
- @Override
- public int getMinHeight() {
- return BoxLayout.computeMinHeightHorizontal(this);
- }
-
- @Override
- public int getPreferredInnerWidth() {
- return BoxLayout.computePreferredWidthVertical(this);
- }
-
- @Override
- public int getPreferredInnerHeight() {
- return BoxLayout.computePreferredHeightHorizontal(this);
- }
-
- @Override
- protected void layout() {
- layoutChildrenFullInnerArea();
- }
}
class AC implements Callable, Runnable {
@@ -1362,7 +1415,7 @@ class AC implements Callable, Runnable {
private V result;
private Exception exception;
- public AC(Callable jobC, Runnable jobR, AsyncCompletionListener listener) {
+ AC(Callable jobC, Runnable jobR, AsyncCompletionListener listener) {
this.jobC = jobC;
this.jobR = jobR;
this.listener = listener;
@@ -1398,7 +1451,7 @@ static class TF implements ThreadFactory {
final AtomicInteger threadNumber = new AtomicInteger(1);
final String prefix;
- public TF() {
+ TF() {
this.prefix = "GUI-" + poolNumber.getAndIncrement() + "-invokeAsync-";
}
diff --git a/twl/src/de/matthiasmann/twl/InfoWindow.java b/twl/src/de/matthiasmann/twl/InfoWindow.java
index 8b39cf9..7304616 100644
--- a/twl/src/de/matthiasmann/twl/InfoWindow.java
+++ b/twl/src/de/matthiasmann/twl/InfoWindow.java
@@ -36,7 +36,7 @@
*
* @author Matthias Mann
*/
-public class InfoWindow extends Widget {
+public class InfoWindow extends Container {
private final Widget owner;
@@ -85,31 +85,6 @@ public void closeInfo() {
protected void infoWindowClosed() {
}
- @Override
- protected void layout() {
- layoutChildrenFullInnerArea();
- }
-
- @Override
- public int getMinWidth() {
- return BoxLayout.computeMinWidthVertical(this);
- }
-
- @Override
- public int getMinHeight() {
- return BoxLayout.computeMinHeightHorizontal(this);
- }
-
- @Override
- public int getPreferredInnerWidth() {
- return BoxLayout.computePreferredWidthVertical(this);
- }
-
- @Override
- public int getPreferredInnerHeight() {
- return BoxLayout.computePreferredHeightHorizontal(this);
- }
-
private static boolean isParentInfoWindow(Widget w) {
while(w != null) {
if(w instanceof InfoWindow) {
diff --git a/twl/src/de/matthiasmann/twl/ListBox.java b/twl/src/de/matthiasmann/twl/ListBox.java
index d05ef49..bf2c500 100644
--- a/twl/src/de/matthiasmann/twl/ListBox.java
+++ b/twl/src/de/matthiasmann/twl/ListBox.java
@@ -29,9 +29,11 @@
*/
package de.matthiasmann.twl;
+import de.matthiasmann.twl.model.IntegerModel;
import de.matthiasmann.twl.utils.CallbackSupport;
import de.matthiasmann.twl.model.ListModel;
import de.matthiasmann.twl.model.ListModel.ChangeListener;
+import de.matthiasmann.twl.model.ListSelectionModel;
import de.matthiasmann.twl.renderer.AnimationState.StateKey;
/**
@@ -47,7 +49,7 @@ public class ListBox extends Widget {
* @see #setSelected(int)
* @see #setSelected(int, boolean)
*/
- public static final int NO_SELECTION = -1;
+ public static final int NO_SELECTION = ListSelectionModel.NO_SELECTION;
public static final int DEFAULT_CELL_HEIGHT = 20;
public static final int SINGLE_COLUMN = -1;
@@ -75,17 +77,21 @@ public boolean actionRequested() {
private final Scrollbar scrollbar;
private ListBoxDisplay[] labels;
private ListModel model;
+ private IntegerModel selectionModel;
+ private Runnable selectionModelCallback;
private int cellHeight = DEFAULT_CELL_HEIGHT;
private int cellWidth = SINGLE_COLUMN;
private boolean rowMajor = true;
private boolean fixedCellWidth;
private boolean fixedCellHeight;
+ private int minDisplayedRows = 1;
private int numCols = 1;
private int firstVisible;
private int selected = NO_SELECTION;
private int numEntries;
private boolean needUpdate;
+ private boolean inSetSelected;
private CallbackWithReason>[] callbacks;
public ListBox() {
@@ -109,6 +115,12 @@ public ListBox(ListModel model) {
setModel(model);
}
+ @SuppressWarnings("OverridableMethodCallInConstructor")
+ public ListBox(ListSelectionModel model) {
+ this();
+ setModel(model);
+ }
+
public ListModel getModel() {
return model;
}
@@ -126,6 +138,40 @@ public void setModel(ListModel model) {
}
}
+ public IntegerModel getSelectionModel() {
+ return selectionModel;
+ }
+
+ public void setSelectionModel(IntegerModel selectionModel) {
+ if(this.selectionModel != selectionModel) {
+ if(this.selectionModel != null) {
+ this.selectionModel.removeCallback(selectionModelCallback);
+ }
+ this.selectionModel = selectionModel;
+ if(selectionModel != null) {
+ if(selectionModelCallback == null) {
+ selectionModelCallback = new Runnable() {
+ public void run() {
+ syncSelectionFromModel();
+ }
+ };
+ }
+ this.selectionModel.addCallback(selectionModelCallback);
+ syncSelectionFromModel();
+ }
+ }
+ }
+
+ public void setModel(ListSelectionModel model) {
+ setSelectionModel(null);
+ if(model == null) {
+ setModel((ListModel)null);
+ } else {
+ setModel(model.getListModel());
+ setSelectionModel(model);
+ }
+ }
+
public void addCallback(CallbackWithReason cb) {
callbacks = CallbackSupport.addCallbackToList(callbacks, cb, CallbackWithReason.class);
}
@@ -220,7 +266,7 @@ public void setSelected(int selected) {
* Selects the specified entry and optionally scrolls to that entry
*
* @param selected the index or {@link #NO_SELECTION}
- * @param scroll treu if it should scroll to make the entry visible
+ * @param scroll true if it should scroll to make the entry visible
* @throws IllegalArgumentException if index is invalid
*/
public void setSelected(int selected, boolean scroll) {
@@ -251,6 +297,14 @@ void setSelected(int selected, boolean scroll, CallbackReason reason) {
}
if(this.selected != selected) {
this.selected = selected;
+ if(selectionModel != null) {
+ try {
+ inSetSelected = true;
+ selectionModel.setValue(selected);
+ } finally {
+ inSetSelected = false;
+ }
+ }
needUpdate = true;
doCallback(reason);
} else if(reason.actionRequested() || reason == CallbackReason.MOUSE_CLICK) {
@@ -341,6 +395,7 @@ protected void applyTheme(ThemeInfo themeInfo) {
setRowMajor(themeInfo.getParameter("rowMajor", true));
setFixedCellWidth(themeInfo.getParameter("fixedCellWidth", false));
setFixedCellHeight(themeInfo.getParameter("fixedCellHeight", false));
+ minDisplayedRows = themeInfo.getParameter("minDisplayedRows", 1);
}
protected void goKeyboard(int dir) {
@@ -441,7 +496,12 @@ public int getMinWidth() {
@Override
public int getMinHeight() {
- return Math.max(super.getMinHeight(), scrollbar.getMinHeight());
+ int minHeight = Math.max(super.getMinHeight(), scrollbar.getMinHeight());
+ if(minDisplayedRows > 0) {
+ minHeight = Math.max(minHeight, getBorderVertical() +
+ Math.min(numEntries, minDisplayedRows) * cellHeight);
+ }
+ return minHeight;
}
@Override
@@ -724,6 +784,12 @@ void scrollbarChanged() {
setFirstVisible(scrollbar.getValue() * numCols);
}
+ void syncSelectionFromModel() {
+ if(!inSetSelected) {
+ setSelected(selectionModel.getValue());
+ }
+ }
+
private class LImpl implements ChangeListener, Runnable {
public void entriesInserted(int first, int last) {
ListBox.this.entriesInserted(first, last);
diff --git a/twl/src/de/matthiasmann/twl/Menu.java b/twl/src/de/matthiasmann/twl/Menu.java
index 81d8939..d5dc186 100644
--- a/twl/src/de/matthiasmann/twl/Menu.java
+++ b/twl/src/de/matthiasmann/twl/Menu.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2008-2010, Matthias Mann
+ * Copyright (c) 2008-2011, Matthias Mann
*
* All rights reserved.
*
@@ -31,6 +31,8 @@
import de.matthiasmann.twl.model.BooleanModel;
import de.matthiasmann.twl.renderer.AnimationState.StateKey;
+import de.matthiasmann.twl.utils.CallbackSupport;
+import de.matthiasmann.twl.utils.TypeMapping;
import java.util.ArrayList;
import java.util.Iterator;
@@ -43,8 +45,38 @@ public class Menu extends MenuElement implements Iterable {
public static final StateKey STATE_HAS_OPEN_MENUS = StateKey.get("hasOpenMenus");
+ public interface Listener {
+ /**
+ * Called before a menu popup is created.
+ *
This is only called once until all open popups are closed.
+ *
When a menu is displayed as menu bar then no events are fired.
+ *
+ * @param menu the {@code Menu} which for which a popup will be opened
+ */
+ public void menuOpening(Menu menu);
+
+ /**
+ * Called after a popup has been opened.
+ *
When a menu is displayed as menu bar then no events are fired.
+ *
+ * @param menu the {@code Menu} which has been opened
+ * @see #menuOpening(de.matthiasmann.twl.Menu)
+ */
+ public void menuOpened(Menu menu);
+
+ /**
+ * Called after a popup has been closed.
+ *
When a menu is displayed as menu bar then no events are fired.
+ *
+ * @param menu the {@code Menu} which has been closed
+ */
+ public void menuClosed(Menu menu);
+ }
+
private final ArrayList elements = new ArrayList();
+ private final TypeMapping classAlignments = new TypeMapping();
private String popupTheme;
+ private Listener[] listeners;
/**
* Creates a new menu without name.
@@ -69,6 +101,14 @@ public Menu() {
public Menu(String name) {
super(name);
}
+
+ public void addListener(Listener listener) {
+ listeners = CallbackSupport.addCallbackToList(listeners, listener, Listener.class);
+ }
+
+ public void removeListener(Listener listener) {
+ listeners = CallbackSupport.removeCallbackFromList(listeners, listener);
+ }
/**
* Returns the theme which is used when this menu is displayed as popup/sub menu.
@@ -89,7 +129,40 @@ public void setPopupTheme(String popupTheme) {
}
/**
- * Returns a mutable iterator which iterators over all menu elements
+ * Sets the default alignment based on menu element subclasses.
+ *
By default all alignments are {@link Alignment#FILL}
+ *
+ * @param clazz the class for which a default alignment should be set
+ * @param value the alignment
+ */
+ public void setClassAlignment(Class extends MenuElement> clazz, Alignment value) {
+ if(value == null) {
+ throw new NullPointerException("value");
+ }
+ if(value == Alignment.FILL) {
+ classAlignments.remove(clazz);
+ } else {
+ classAlignments.put(clazz, value);
+ }
+ }
+
+ /**
+ * Retrieves the default alignment for the given menu element class.
+ *
By default all alignments are {@link Alignment#FILL}
+ *
+ * @param clazz the menu element class
+ * @return the alignment
+ */
+ public Alignment getClassAlignment(Class extends MenuElement> clazz) {
+ Alignment alignment = classAlignments.get(clazz);
+ if(alignment == null) {
+ return Alignment.FILL;
+ }
+ return alignment;
+ }
+
+ /**
+ * Returns a mutable iterator which iterates over all menu elements
* @return a iterator
*/
public Iterator iterator() {
@@ -175,7 +248,7 @@ public Menu addSpacer() {
* @see #createMenuBar()
*/
public void createMenuBar(Widget container) {
- MenuManager mm = new MenuManager(container, true);
+ MenuManager mm = createMenuManager(container, true);
for(Widget w : createWidgets(mm, 0)) {
container.add(w);
}
@@ -191,12 +264,23 @@ public Widget createMenuBar() {
DialogLayout l = new DialogLayout();
setWidgetTheme(l, "menubar");
- MenuManager mm = new MenuManager(l, true);
+ MenuManager mm = createMenuManager(l, true);
Widget[] widgets = createWidgets(mm, 0);
l.setHorizontalGroup(l.createSequentialGroup().addWidgetsWithGap("menuitem", widgets));
l.setVerticalGroup(l.createParallelGroup(widgets));
+ for(int i=0,n=elements.size() ; ithis callback is invoked after the menu is closed.
+ *
+ * @param cb the callback (can be null)
+ */
public void setCallback(Runnable cb) {
this.cb = cb;
}
@@ -62,10 +77,11 @@ protected Widget createMenuWidget(MenuManager mm, int level) {
Button b = new MenuBtn();
setWidgetTheme(b, "button");
+ b.addCallback(mm.getCloseCallback());
+
if(cb != null) {
b.addCallback(cb);
}
- b.addCallback(mm.getCloseCallback());
return b;
}
diff --git a/twl/src/de/matthiasmann/twl/MenuElement.java b/twl/src/de/matthiasmann/twl/MenuElement.java
index f379d29..6052ef1 100644
--- a/twl/src/de/matthiasmann/twl/MenuElement.java
+++ b/twl/src/de/matthiasmann/twl/MenuElement.java
@@ -44,6 +44,7 @@ public abstract class MenuElement {
private boolean enabled = true;
private Object tooltipContent;
private PropertyChangeSupport pcs;
+ private Alignment alignment;
public MenuElement() {
}
@@ -96,6 +97,27 @@ public MenuElement setTooltipContent(Object tooltip) {
return this;
}
+ public Alignment getAlignment() {
+ return alignment;
+ }
+
+ /**
+ * Sets the alignment used for this element in the menubar.
+ * The default value is {@code null} which means that the class based
+ * default is used.
+ *
+ * @param alignment the alignment or null.
+ * @return this
+ * @see Menu#setClassAlignment(java.lang.Class, de.matthiasmann.twl.Alignment)
+ * @see Menu#getClassAlignment(java.lang.Class)
+ */
+ public MenuElement setAlignment(Alignment alignment) {
+ Alignment oldAlignment = this.alignment;
+ this.alignment = alignment;
+ firePropertyChange("alignment", oldAlignment, alignment);
+ return this;
+ }
+
protected abstract Widget createMenuWidget(MenuManager mm, int level);
public void addPropertyChangeListener(PropertyChangeListener listener) {
@@ -142,7 +164,13 @@ protected void firePropertyChange(String propertyName, Object oldValue, Object n
}
}
- void setWidgetTheme(Widget w, String defaultTheme) {
+ /**
+ * Helper method to apply the theme from the menu element to the widget
+ * if it was set, otherwise the defaultTheme is used.
+ * @param w the Widget to which the theme should be applied
+ * @param defaultTheme the defaultTheme when none was set
+ */
+ protected void setWidgetTheme(Widget w, String defaultTheme) {
if(theme != null) {
w.setTheme(theme);
} else {
diff --git a/twl/src/de/matthiasmann/twl/MenuManager.java b/twl/src/de/matthiasmann/twl/MenuManager.java
index 3314a0b..ffaa97b 100644
--- a/twl/src/de/matthiasmann/twl/MenuManager.java
+++ b/twl/src/de/matthiasmann/twl/MenuManager.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2008-2010, Matthias Mann
+ * Copyright (c) 2008-2012, Matthias Mann
*
* All rights reserved.
*
@@ -150,6 +150,15 @@ public void closePopup() {
gui.resendLastMouseMove();
}
}
+
+ /**
+ * Returns the popup widget for the specified menu
+ * @param menu the menu for which to return the popup
+ * @return the popup widget or null if not open
+ */
+ public Widget getPopupForMenu(Menu menu) {
+ return popups.get(menu);
+ }
@Override
protected void afterAddToGUI(GUI gui) {
@@ -212,7 +221,7 @@ Widget getWidgetUnderMouse() {
}
void popupTimer() {
- if(lastMouseOverWidget instanceof Menu.SubMenuBtn) {
+ if((lastMouseOverWidget instanceof Menu.SubMenuBtn) && lastMouseOverWidget.isEnabled()) {
((Menu.SubMenuBtn)lastMouseOverWidget).run();
} else if(lastMouseOverWidget != this) {
int level = 0;
diff --git a/twl/src/de/matthiasmann/twl/PopupWindow.java b/twl/src/de/matthiasmann/twl/PopupWindow.java
index e6ac978..d75982f 100644
--- a/twl/src/de/matthiasmann/twl/PopupWindow.java
+++ b/twl/src/de/matthiasmann/twl/PopupWindow.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2008-2010, Matthias Mann
+ * Copyright (c) 2008-2012, Matthias Mann
*
* All rights reserved.
*
@@ -56,13 +56,14 @@
* @see #openPopup()
* @see #layoutChildrenFullInnerArea()
*/
-public class PopupWindow extends Widget {
+public class PopupWindow extends Container {
private final Widget owner;
private boolean closeOnClickedOutside = true;
private boolean closeOnEscape = true;
-
+ private Runnable requestCloseCallback;
+
/**
* Creates a new pop-up window.
*
@@ -111,6 +112,20 @@ public void setCloseOnEscape(boolean closeOnEscape) {
this.closeOnEscape = closeOnEscape;
}
+ public Runnable getRequestCloseCallback() {
+ return requestCloseCallback;
+ }
+
+ /**
+ * Sets a callback to be called when {@link #requestPopupClose()} is executed.
+ * This will override the default behavior of closing the popup.
+ *
+ * @param requestCloseCallback the new callback or null
+ */
+ public void setRequestCloseCallback(Runnable requestCloseCallback) {
+ this.requestCloseCallback = requestCloseCallback;
+ }
+
/**
* Opens the pop-up window with it's current size and position.
* In order for this to work the owner must be part of the GUI tree.
@@ -224,16 +239,6 @@ public boolean bindMouseDrag(Runnable cb) {
return false;
}
- @Override
- public int getPreferredInnerWidth() {
- return BoxLayout.computePreferredWidthVertical(this);
- }
-
- @Override
- public int getPreferredInnerHeight() {
- return BoxLayout.computePreferredHeightHorizontal(this);
- }
-
@Override
public int getPreferredWidth() {
int parentWidth = (getParent() != null) ? getParent().getInnerWidth() : Short.MAX_VALUE;
@@ -245,12 +250,14 @@ public int getPreferredHeight() {
int parentHeight = (getParent() != null) ? getParent().getInnerHeight() : Short.MAX_VALUE;
return Math.min(parentHeight, super.getPreferredHeight());
}
-
- @Override
- protected void layout() {
- layoutChildrenFullInnerArea();
- }
+ /**
+ * This method is final to ensure correct event handling for pop-ups.
+ * To customize the event handling override the {@link #handleEventPopup(de.matthiasmann.twl.Event) }.
+ *
+ * @param evt the event
+ * @return always returns true
+ */
@Override
protected final boolean handleEvent(Event evt) {
if(handleEventPopup(evt)) {
@@ -271,6 +278,14 @@ protected final boolean handleEvent(Event evt) {
return true;
}
+ /**
+ * This method can be overriden to customize the event handling of a
+ * pop-up window.
+ *
The default implementation calls {@link Widget#handleEvent(de.matthiasmann.twl.Event) }
+ *
+ * @param evt the event
+ * @return true if the event has been handled, false otherwise.
+ */
protected boolean handleEventPopup(Event evt) {
return super.handleEvent(evt);
}
@@ -285,11 +300,18 @@ protected final boolean isMouseInside(Event evt) {
* Also called by the default implementation of {@code mouseClickedOutside}
* when {@code closeOnClickedOutside} is active.
*
+ *
By default it calls {@link #closePopup() } except when a
+ * {@link #setRequestCloseCallback(java.lang.Runnable) } had been set.
+ *
* @see #setCloseOnEscape(boolean)
* @see #mouseClickedOutside(de.matthiasmann.twl.Event)
*/
protected void requestPopupClose() {
- closePopup();
+ if(requestCloseCallback != null) {
+ requestCloseCallback.run();
+ } else {
+ closePopup();
+ }
}
/**
@@ -306,4 +328,12 @@ protected void mouseClickedOutside(Event evt) {
requestPopupClose();
}
}
+
+ @Override
+ void setParent(Widget parent) {
+ if(!(parent instanceof GUI)) {
+ throw new IllegalArgumentException("PopupWindow can't be used as child widget");
+ }
+ super.setParent(parent);
+ }
}
diff --git a/twl/src/de/matthiasmann/twl/PropertySheet.java b/twl/src/de/matthiasmann/twl/PropertySheet.java
index 2a675e8..12d8ad9 100644
--- a/twl/src/de/matthiasmann/twl/PropertySheet.java
+++ b/twl/src/de/matthiasmann/twl/PropertySheet.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2008-2010, Matthias Mann
+ * Copyright (c) 2008-2012, Matthias Mann
*
* All rights reserved.
*
@@ -54,6 +54,22 @@ public interface PropertyEditor {
public void valueChanged();
public void preDestroy();
public void setSelected(boolean selected);
+
+ /**
+ * Can be used to position the widget in a cell.
+ *
If this method returns false, the table will position the widget itself.
+ *
+ *
This method is responsible to call setPosition and setSize on the
+ * widget or return false.
+ *
+ * @param x the left edge of the cell
+ * @param y the top edge of the cell
+ * @param width the width of the cell
+ * @param height the height of the cell
+ *
+ * @return true if the position was changed by this method.
+ */
+ public boolean positionWidget(int x, int y, int width, int height);
}
public interface PropertyEditorFactory {
@@ -69,6 +85,7 @@ public PropertySheet() {
this(new Model());
}
+ @SuppressWarnings("OverridableMethodCallInConstructor")
private PropertySheet(Model model) {
super(model);
this.rootList = new SimplePropertyList("");
@@ -162,6 +179,7 @@ interface PSTreeTableNode extends TreeTableNode {
static abstract class PropertyNode extends AbstractTreeTableNode implements Runnable, PSTreeTableNode {
protected final Property> property;
+ @SuppressWarnings("LeakingThisInConstructor")
public PropertyNode(TreeTableNode parent, Property> property) {
super(parent);
this.property = property;
@@ -256,6 +274,7 @@ class PropertyListCellRenderer extends TreeNodeCellRenderer {
public PropertyListCellRenderer() {
bgRenderer = new Widget();
textRenderer = new Label(bgRenderer.getAnimationState());
+ textRenderer.setAutoSize(false);
bgRenderer.add(textRenderer);
bgRenderer.setTheme(getTheme());
}
@@ -279,7 +298,7 @@ public void setCellData(int row, int column, Object data, NodeState nodeState) {
textRenderer.setText((String)data);
}
@Override
- protected void setSubRenderer(Object colData) {
+ protected void setSubRenderer(int row, int column, Object colData) {
}
}
@@ -308,8 +327,10 @@ public Widget updateWidget(Widget existingWidget) {
return editor.getWidget();
}
public void positionWidget(Widget widget, int x, int y, int w, int h) {
- widget.setPosition(x, y);
- widget.setSize(w, h);
+ if(!editor.positionWidget(x, y, w, h)) {
+ widget.setPosition(x, y);
+ widget.setSize(w, h);
+ }
}
}
@@ -337,6 +358,7 @@ static class StringEditor implements PropertyEditor, EditField.Callback {
private final EditField editField;
private final Property property;
+ @SuppressWarnings("LeakingThisInConstructor")
public StringEditor(Property property) {
this.property = property;
this.editField = new EditField();
@@ -371,6 +393,9 @@ private void resetValue() {
editField.setErrorMessage(null);
editField.setReadOnly(property.isReadOnly());
}
+ public boolean positionWidget(int x, int y, int width, int height) {
+ return false;
+ }
}
static class StringEditorFactory implements PropertyEditorFactory {
public PropertyEditor createEditor(Property property) {
@@ -383,6 +408,7 @@ public static class ComboBoxEditor implements PropertyEditor, Runnable {
protected final Property property;
protected final ListModel model;
+ @SuppressWarnings({"LeakingThisInConstructor", "OverridableMethodCallInConstructor"})
public ComboBoxEditor(Property property, ListModel model) {
this.property = property;
this.comboBox = new ComboBox(model);
@@ -422,6 +448,9 @@ protected int findEntry(T value) {
}
return -1;
}
+ public boolean positionWidget(int x, int y, int width, int height) {
+ return false;
+ }
}
public static class ComboBoxEditorFactory implements PropertyEditorFactory {
private final ModelForwarder modelForwarder;
@@ -439,6 +468,7 @@ public PropertyEditor createEditor(Property property) {
}
class ModelForwarder extends AbstractListModel implements ListModel.ChangeListener {
private ListModel model;
+ @SuppressWarnings("OverridableMethodCallInConstructor")
public ModelForwarder(ListModel model) {
setModel(model);
}
diff --git a/twl/src/de/matthiasmann/twl/Rect.java b/twl/src/de/matthiasmann/twl/Rect.java
index b3ededb..b980423 100644
--- a/twl/src/de/matthiasmann/twl/Rect.java
+++ b/twl/src/de/matthiasmann/twl/Rect.java
@@ -44,10 +44,12 @@ public class Rect {
public Rect() {
}
+ @SuppressWarnings("OverridableMethodCallInConstructor")
public Rect(int x, int y, int w, int h) {
setXYWH(x, y, w, h);
}
+ @SuppressWarnings("OverridableMethodCallInConstructor")
public Rect(Rect src) {
set(src.getX(), src.getY(), src.getRight(), src.getBottom());
}
@@ -66,6 +68,13 @@ public void set(int x0, int y0, int x1, int y1) {
this.y1 = y1;
}
+ public void set(Rect src) {
+ this.x0 = src.x0;
+ this.y0 = src.y0;
+ this.x1 = src.x1;
+ this.y1 = src.y1;
+ }
+
/**
* Computes the intersection of this rectangle with the other rectangle.
* If they don't overlapp then this rect will be set to zero width and height.
diff --git a/twl/src/de/matthiasmann/twl/ResizableFrame.java b/twl/src/de/matthiasmann/twl/ResizableFrame.java
index fc63b56..f8d792b 100644
--- a/twl/src/de/matthiasmann/twl/ResizableFrame.java
+++ b/twl/src/de/matthiasmann/twl/ResizableFrame.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2008, Matthias Mann
+ * Copyright (c) 2008-2012, Matthias Mann
*
* All rights reserved.
*
@@ -34,8 +34,15 @@
import de.matthiasmann.twl.utils.TintAnimator;
/**
- * A resizable frame
+ * A resizable frame.
*
+ *
All child widgets (which are not part of the frame itself) cover
+ * the complete inner area {@link #layoutChildFullInnerArea(de.matthiasmann.twl.Widget) }.
+ *
+ *
The preferred way to use the ResizableFrame is to add a single
+ * widget which will manage the layout of all it's children.
+ * {@link DialogLayout} can be used for this to avoid creating a new class.
+ *
* @author Matthias Mann
*/
public class ResizableFrame extends Widget {
@@ -78,6 +85,7 @@ private enum DragMode {
private final MouseCursor[] cursors;
private ResizableAxis resizableAxis = ResizableAxis.BOTH;
+ private boolean draggable = true;
private DragMode dragMode = DragMode.NONE;
private int dragStartX;
private int dragStartY;
@@ -140,6 +148,23 @@ public void setResizableAxis(ResizableAxis resizableAxis) {
}
}
+ public boolean isDraggable() {
+ return draggable;
+ }
+
+ /**
+ * Controls weather the ResizableFrame can be dragged via the title bar or
+ * not, default is true.
+ *
+ *
When set to false the resizing should also be disabled to present a
+ * consistent behavior to the user.
+ *
+ * @param movable if dragging via the title bar is allowed - default is true.
+ */
+ public void setDraggable(boolean movable) {
+ this.draggable = movable;
+ }
+
public boolean hasTitleBar() {
return titleWidget != null && titleWidget.getParent() == this;
}
@@ -163,6 +188,23 @@ public void removeCloseCallback(Runnable cb) {
}
}
+ public int getFadeDurationActivate() {
+ return fadeDurationActivate;
+ }
+
+ public int getFadeDurationDeactivate() {
+ return fadeDurationDeactivate;
+ }
+
+ public int getFadeDurationHide() {
+ return fadeDurationHide;
+ }
+
+ public int getFadeDurationShow() {
+ return fadeDurationShow;
+ }
+
+
@Override
public void setVisible(boolean visible) {
if(visible) {
@@ -186,11 +228,7 @@ public void setHardVisible(boolean visible) {
protected void applyThemeResizableFrame(ThemeInfo themeInfo) {
for(DragMode m : DragMode.values()) {
- if(m.cursorName != null) {
- cursors[m.ordinal()] = themeInfo.getMouseCursor(m.cursorName);
- } else {
- cursors[m.ordinal()] = null;
- }
+ cursors[m.ordinal()] = themeInfo.getMouseCursor(m.cursorName);
}
titleAreaTop = themeInfo.getParameter("titleAreaTop", 0);
titleAreaLeft = themeInfo.getParameter("titleAreaLeft", 0);
@@ -233,7 +271,7 @@ protected void updateTintAnimation() {
protected void fadeTo(Color color, int duration) {
//System.out.println("Start fade to " + color + " over " + duration + " ms");
allocateTint().fadeTo(color, duration);
- if(!super.isVisible() && color.getA() != 0) {
+ if(!super.isVisible() && color.getAlpha() != 0) {
setHardVisible(true);
}
}
@@ -395,6 +433,42 @@ public int getMinHeight() {
return minHeight;
}
+ @Override
+ public int getMaxWidth() {
+ int maxWidth = super.getMaxWidth();
+ for(int i=0,n=getNumChildren() ; i 0) {
+ aMaxWidth += getBorderHorizontal();
+ if(maxWidth == 0 || aMaxWidth < maxWidth) {
+ maxWidth = aMaxWidth;
+ }
+ }
+ }
+ }
+ return maxWidth;
+ }
+
+ @Override
+ public int getMaxHeight() {
+ int maxHeight = super.getMaxHeight();
+ for(int i=0,n=getNumChildren() ; i 0) {
+ aMaxHeight += getBorderVertical();
+ if(maxHeight == 0 || aMaxHeight < maxHeight) {
+ maxHeight = aMaxHeight;
+ }
+ }
+ }
+ }
+ return maxHeight;
+ }
+
@Override
public int getPreferredInnerWidth() {
int prefWidth = 0;
@@ -460,10 +534,6 @@ protected boolean handleEvent(Event evt) {
return true;
}
- DragMode cursorMode = getDragMode(evt.getMouseX(), evt.getMouseY());
- MouseCursor cursor = cursors[cursorMode.ordinal()];
- setMouseCursor(cursor);
-
if(!isMouseExit && resizeHandle != null && resizeHandle.isVisible()) {
resizeHandle.getAnimationState().setAnimationState(
TextWidget.STATE_HOVER, resizeHandle.isMouseInside(evt));
@@ -473,7 +543,6 @@ protected boolean handleEvent(Event evt) {
if(evt.getType() == Event.Type.MOUSE_BTNDOWN &&
evt.getMouseButton() == Event.MOUSE_LBUTTON &&
handleMouseDown(evt)) {
- setMouseCursor(cursors[dragMode.ordinal()]);
return true;
}
}
@@ -485,6 +554,19 @@ protected boolean handleEvent(Event evt) {
return evt.isMouseEvent();
}
+ @Override
+ public MouseCursor getMouseCursor(Event evt) {
+ DragMode cursorMode = dragMode;
+ if(cursorMode == DragMode.NONE) {
+ cursorMode = getDragMode(evt.getMouseX(), evt.getMouseY());
+ if(cursorMode == DragMode.NONE) {
+ return getMouseCursor();
+ }
+ }
+
+ return cursors[cursorMode.ordinal()];
+ }
+
private DragMode getDragMode(int mx, int my) {
boolean left = mx < getInnerX();
boolean right = mx >= getInnerRight();
@@ -494,7 +576,11 @@ private DragMode getDragMode(int mx, int my) {
if(titleWidget != null && titleWidget.getParent() == this) {
if(titleWidget.isInside(mx, my)) {
- return DragMode.POSITION;
+ if(draggable) {
+ return DragMode.POSITION;
+ } else {
+ return DragMode.NONE;
+ }
}
top = my < titleWidget.getY();
}
@@ -635,7 +721,7 @@ private void handleMouseDrag(Event evt) {
case POSITION:
if(getParent() != null) {
int minY = getParent().getInnerY();
- int maxY = getParent().getInnerHeight();
+ int maxY = getParent().getInnerBottom();
int height = dragInitialBottom - dragInitialTop;
top = Math.max(minY, Math.min(maxY - height, top + dy));
bottom = Math.min(maxY, Math.max(minY + height, bottom + dy));
diff --git a/twl/src/de/matthiasmann/twl/Scrollbar.java b/twl/src/de/matthiasmann/twl/Scrollbar.java
index c7b0387..eb13fc6 100644
--- a/twl/src/de/matthiasmann/twl/Scrollbar.java
+++ b/twl/src/de/matthiasmann/twl/Scrollbar.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2008, Matthias Mann
+ * Copyright (c) 2008-2011, Matthias Mann
*
* All rights reserved.
*
@@ -29,6 +29,7 @@
*/
package de.matthiasmann.twl;
+import de.matthiasmann.twl.model.IntegerModel;
import de.matthiasmann.twl.renderer.Image;
import de.matthiasmann.twl.utils.CallbackSupport;
@@ -62,14 +63,15 @@ public enum Orientation {
private final Button btnUpLeft;
private final Button btnDownRight;
private final DraggableButton thumb;
- private final DraggableButton.DragListener dragListener;
- private final Runnable timerCallback;
+ private final L dragTimerCB;
private Timer timer;
private int trackClicked;
private int trackClickLimit;
private Runnable[] callbacks;
private Image trackImageUpLeft;
private Image trackImageDownRight;
+ private IntegerModel model;
+ private Runnable modelCB;
private int pageSize;
private int stepSize;
@@ -105,25 +107,7 @@ public void run() {
btnDownRight.setTheme("downbutton");
}
- dragListener = new DraggableButton.DragListener() {
- private int startValue;
- public void dragStarted() {
- startValue = getValue();
- }
- public void dragged(int deltaX, int deltaY) {
- int mouseDelta;
- if(getOrientation() == Orientation.HORIZONTAL) {
- mouseDelta = deltaX;
- } else {
- mouseDelta = deltaY;
- }
- int delta = (getMaxValue() - getMinValue()) * mouseDelta / calcThumbArea();
- int newValue = range(startValue + delta);
- setValue(newValue);
- }
- public void dragStopped() {
- }
- };
+ dragTimerCB = new L();
btnUpLeft.setCanAcceptKeyboardFocus(false);
btnUpLeft.getModel().addStateCallback(cbUpdateTimer);
@@ -131,13 +115,7 @@ public void dragStopped() {
btnDownRight.getModel().addStateCallback(cbUpdateTimer);
thumb.setCanAcceptKeyboardFocus(false);
thumb.setTheme("thumb");
- thumb.setListener(dragListener);
-
- timerCallback = new Runnable() {
- public void run() {
- onTimer(REPEAT_DELAY);
- }
- };
+ thumb.setListener(dragTimerCB);
add(btnUpLeft);
add(btnDownRight);
@@ -167,6 +145,30 @@ public Orientation getOrientation() {
return orientation;
}
+ public IntegerModel getModel() {
+ return model;
+ }
+
+ public void setModel(IntegerModel model) {
+ if(this.model != model) {
+ if(this.model != null) {
+ this.model.removeCallback(modelCB);
+ }
+ this.model = model;
+ if(model != null) {
+ if(modelCB == null) {
+ modelCB = new Runnable() {
+ public void run() {
+ syncModel();
+ }
+ };
+ }
+ model.addCallback(modelCB);
+ syncModel();
+ }
+ }
+ }
+
public int getValue() {
return value;
}
@@ -182,6 +184,9 @@ public void setValue(int value, boolean fireCallbacks) {
this.value = value;
setThumbPos();
firePropertyChange("value", oldValue, value);
+ if(model != null) {
+ model.setValue(value);
+ }
if(fireCallbacks) {
doCallback();
}
@@ -288,15 +293,15 @@ public void setScaleThumb(boolean scaleThumb) {
public void externalDragStart() {
thumb.getAnimationState().setAnimationState(Button.STATE_PRESSED, true);
- dragListener.dragStarted();
+ dragTimerCB.dragStarted();
}
public void externalDragged(int deltaX, int deltaY) {
- dragListener.dragged(deltaX, deltaY);
+ dragTimerCB.dragged(deltaX, deltaY);
}
public void externalDragStopped() {
- dragListener.dragStopped();
+ // dragTimerCB.dragStopped(); (it's empty anyway)
thumb.getAnimationState().setAnimationState(Button.STATE_PRESSED, false);
}
@@ -366,13 +371,20 @@ protected void paintWidget(GUI gui) {
protected void afterAddToGUI(GUI gui) {
super.afterAddToGUI(gui);
timer = gui.createTimer();
- timer.setCallback(timerCallback);
+ timer.setCallback(dragTimerCB);
timer.setContinuous(true);
+ if(model != null) {
+ // modelCB is created when the model was set
+ model.addCallback(modelCB);
+ }
}
@Override
protected void beforeRemoveFromGUI(GUI gui) {
super.beforeRemoveFromGUI(gui);
+ if(model != null) {
+ model.removeCallback(modelCB);
+ }
if(timer != null) {
timer.stop();
}
@@ -506,13 +518,22 @@ void updateTimer() {
btnDownRight.getModel().isArmed()) {
if(!timer.isRunning()) {
onTimer(INITIAL_DELAY);
- timer.start();
+ // onTimer() can call setValue() which calls user code
+ // that user code could potentially remove the Scrollbar from GUI
+ if(timer != null) {
+ timer.start();
+ }
}
} else {
timer.stop();
}
}
}
+
+ void syncModel() {
+ setMinMaxValue(model.getMinValue(), model.getMaxValue());
+ setValue(model.getValue());
+ }
@Override
public int getMinWidth() {
@@ -596,4 +617,27 @@ private void setThumbPos() {
thumb.setPosition(getX(), ypos);
}
}
+
+ final class L implements DraggableButton.DragListener, Runnable {
+ private int startValue;
+ public void dragStarted() {
+ startValue = getValue();
+ }
+ public void dragged(int deltaX, int deltaY) {
+ int mouseDelta;
+ if(getOrientation() == Orientation.HORIZONTAL) {
+ mouseDelta = deltaX;
+ } else {
+ mouseDelta = deltaY;
+ }
+ int delta = (getMaxValue() - getMinValue()) * mouseDelta / calcThumbArea();
+ int newValue = range(startValue + delta);
+ setValue(newValue);
+ }
+ public void dragStopped() {
+ }
+ public void run() {
+ onTimer(REPEAT_DELAY);
+ }
+ };
}
diff --git a/twl/src/de/matthiasmann/twl/SimpleDialog.java b/twl/src/de/matthiasmann/twl/SimpleDialog.java
index a688612..f5b9ba1 100644
--- a/twl/src/de/matthiasmann/twl/SimpleDialog.java
+++ b/twl/src/de/matthiasmann/twl/SimpleDialog.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2008-2010, Matthias Mann
+ * Copyright (c) 2008-2011, Matthias Mann
*
* All rights reserved.
*
@@ -44,6 +44,7 @@ public class SimpleDialog {
private Object msg;
private Runnable cbOk;
private Runnable cbCancel;
+ private boolean focusCancelButton;
public SimpleDialog() {
}
@@ -114,6 +115,20 @@ public void setCancelCallback(Runnable cbCancel) {
this.cbCancel = cbCancel;
}
+ public boolean isFocusCancelButton() {
+ return focusCancelButton;
+ }
+
+ /**
+ * Should the cancel button be focused when the dialog is created?
+ * Default is false (eg focus the message or the OK button).
+ *
+ * @param focusCancelButton true to focus the cancel button
+ */
+ public void setFocusCancelButton(boolean focusCancelButton) {
+ this.focusCancelButton = focusCancelButton;
+ }
+
/**
* Shows the dialog centered
*
@@ -155,9 +170,12 @@ public PopupWindow showDialog(Widget owner) {
btnOk.setTheme("btnOk");
btnOk.addCallback(new ButtonCB(popupWindow, cbOk));
+ ButtonCB btnCancelCallback = new ButtonCB(popupWindow, cbCancel);
+ popupWindow.setRequestCloseCallback(btnCancelCallback);
+
Button btnCancel = new Button("Cancel");
btnCancel.setTheme("btnCancel");
- btnCancel.addCallback(new ButtonCB(popupWindow, cbCancel));
+ btnCancel.addCallback(btnCancelCallback);
DialogLayout layout = new DialogLayout();
layout.setTheme("content");
@@ -198,7 +216,9 @@ public PopupWindow showDialog(Widget owner) {
popupWindow.add(layout);
popupWindow.openPopupCentered();
- if(msgWidget != null && msgWidget.canAcceptKeyboardFocus()) {
+ if(focusCancelButton) {
+ btnCancel.requestKeyboardFocus();
+ } else if(msgWidget != null && msgWidget.canAcceptKeyboardFocus()) {
msgWidget.requestKeyboardFocus();
}
diff --git a/twl/src/de/matthiasmann/twl/SplitPane.java b/twl/src/de/matthiasmann/twl/SplitPane.java
index 5b8f405..daaac3a 100644
--- a/twl/src/de/matthiasmann/twl/SplitPane.java
+++ b/twl/src/de/matthiasmann/twl/SplitPane.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2008-2010, Matthias Mann
+ * Copyright (c) 2008-2011, Matthias Mann
*
* All rights reserved.
*
@@ -40,11 +40,23 @@ public enum Direction {
int get(int x, int y) {
return x;
}
+ int getMinSize(Widget w) {
+ return w.getMinWidth();
+ }
+ int getPrefSize(Widget w) {
+ return w.getPreferredWidth();
+ }
},
VERTICAL("splitterVertical") {
int get(int x, int y) {
return y;
}
+ int getMinSize(Widget w) {
+ return w.getMinHeight();
+ }
+ int getPrefSize(Widget w) {
+ return w.getPreferredHeight();
+ }
};
final String splitterTheme;
@@ -52,21 +64,44 @@ int get(int x, int y) {
this.splitterTheme = splitterTheme;
}
abstract int get(int x, int y);
+ abstract int getMinSize(Widget w);
+ abstract int getPrefSize(Widget w);
}
+ /**
+ * Magic constant for {@link #setSplitPosition(int) } to keep both
+ * widgets on equal size.
+ */
public static final int CENTER = -1;
+ /**
+ * Magic constant for {@link #setSplitPosition(int) } to keep the first
+ * (or second widget when {@link #getReverseSplitPosition() } is active)
+ * in it's minimum size.
+ */
+ public static final int MIN_SIZE = -2;
+
+ /**
+ * Magic constant for {@link #setSplitPosition(int) } to keep the first
+ * (or second widget when {@link #getReverseSplitPosition() } is active)
+ * in it's preferred size.
+ */
+ public static final int PREFERRED_SIZE = -3;
+
private final DraggableButton splitter;
private Direction direction;
private int splitPosition = CENTER;
private boolean reverseSplitPosition;
+ private boolean respectMinSizes;
+ @SuppressWarnings("OverridableMethodCallInConstructor")
public SplitPane() {
splitter = new DraggableButton();
+ splitter.setCanAcceptKeyboardFocus(false);
splitter.setListener(new DraggableButton.DragListener() {
int initialPos;
public void dragStarted() {
- initialPos = getSplitPosNoCenter();
+ initialPos = getEffectiveSplitPosition();
}
public void dragged(int deltaX, int deltaY) {
SplitPane.this.dragged(initialPos, deltaX, deltaY);
@@ -101,8 +136,11 @@ public int getSplitPosition() {
}
public void setSplitPosition(int pos) {
+ if(pos < PREFERRED_SIZE) {
+ throw new IllegalArgumentException("pos");
+ }
splitPosition = pos;
- invalidateLayout();
+ invalidateLayoutLocally();
}
public boolean getReverseSplitPosition() {
@@ -110,7 +148,21 @@ public boolean getReverseSplitPosition() {
}
public void setReverseSplitPosition(boolean reverseSplitPosition) {
- this.reverseSplitPosition = reverseSplitPosition;
+ if(this.reverseSplitPosition != reverseSplitPosition) {
+ this.reverseSplitPosition = reverseSplitPosition;
+ invalidateLayoutLocally();
+ }
+ }
+
+ public boolean isRespectMinSizes() {
+ return respectMinSizes;
+ }
+
+ public void setRespectMinSizes(boolean respectMinSizes) {
+ if(this.respectMinSizes != respectMinSizes) {
+ this.respectMinSizes = respectMinSizes;
+ invalidateLayoutLocally();
+ }
}
void dragged(int initialPos, int deltaX, int deltaY) {
@@ -199,7 +251,7 @@ protected void layout() {
int innerX = getInnerX();
int innerY = getInnerY();
- int splitPos = getSplitPosNoCenter();
+ int splitPos = getEffectiveSplitPosition();
if(reverseSplitPosition) {
splitPos = getMaxSplitPosition() - splitPos;
@@ -236,12 +288,73 @@ protected void layout() {
}
}
- int getSplitPosNoCenter() {
- if(splitPosition == CENTER) {
- return getMaxSplitPosition()/2;
- } else {
- return clamp(splitPosition);
+ int getEffectiveSplitPosition() {
+ final int maxSplitPosition = getMaxSplitPosition();
+
+ int pos = splitPosition;
+ switch(pos) {
+ case CENTER:
+ pos = maxSplitPosition/2;
+ break;
+ case MIN_SIZE: {
+ Widget w = getPrimaryWidget();
+ if(w != null) {
+ pos = direction.getMinSize(w);
+ } else {
+ pos = maxSplitPosition/2;
+ }
+ break;
+ }
+ case PREFERRED_SIZE: {
+ Widget w = getPrimaryWidget();
+ if(w != null) {
+ pos = direction.getPrefSize(w);
+ } else {
+ pos = maxSplitPosition/2;
+ }
+ break;
+ }
+ }
+
+ int minValue = 0;
+ int maxValue = maxSplitPosition;
+
+ if(respectMinSizes) {
+ Widget a = null;
+ Widget b = null;
+ for(int i=0 ; i idx) {
+ return getChild(idx);
}
+ return null;
}
private int clamp(int pos) {
diff --git a/twl/src/de/matthiasmann/twl/TabbedPane.java b/twl/src/de/matthiasmann/twl/TabbedPane.java
index b3d9b65..fc1de4b 100644
--- a/twl/src/de/matthiasmann/twl/TabbedPane.java
+++ b/twl/src/de/matthiasmann/twl/TabbedPane.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2008-2010, Matthias Mann
+ * Copyright (c) 2008-2012, Matthias Mann
*
* All rights reserved.
*
@@ -92,6 +92,7 @@ public TabbedPane() {
addActionMapping("nextTab", "cycleTabs", +1);
addActionMapping("prevTab", "cycleTabs", -1);
+ setCanAcceptKeyboardFocus(false);
}
public TabPosition getTabPosition() {
@@ -176,6 +177,8 @@ public void setActiveTab(Tab tab) {
}
if(scrollTabs) {
+ validateLayout();
+
int pos, end, size;
if(tabPosition.horz) {
pos = tab.button.getX() - tabBox.getX();
@@ -195,6 +198,10 @@ public void setActiveTab(Tab tab) {
setScrollPos(end - size);
}
}
+
+ if(tab != null && tab.pane != null) {
+ tab.pane.requestKeyboardFocus();
+ }
}
}
@@ -218,6 +225,21 @@ public void removeAllTabs() {
tabs.clear();
activeTab = null;
}
+
+ public int getNumTabs() {
+ return tabs.size();
+ }
+
+ public Tab getTab(int index) {
+ return tabs.get(index);
+ }
+
+ public int getActiveTabIndex() {
+ if(tabs.isEmpty()) {
+ return -1;
+ }
+ return tabs.indexOf(activeTab);
+ }
public void cycleTabs(int direction) {
if(!tabs.isEmpty()) {
@@ -313,29 +335,29 @@ protected void layout() {
switch(tabPosition) {
case TOP:
tabBoxClip.setPosition(getInnerX(), getInnerY());
- tabBoxClip.setSize(getInnerWidth() - scrollCtrlsWidth, tabBoxHeight);
- container.setSize(getInnerWidth(), getInnerHeight() - tabBoxHeight);
+ tabBoxClip.setSize(Math.max(0, getInnerWidth() - scrollCtrlsWidth), tabBoxHeight);
+ container.setSize(getInnerWidth(), Math.max(0, getInnerHeight() - tabBoxHeight));
container.setPosition(getInnerX(), tabBoxClip.getBottom());
break;
case LEFT:
tabBoxClip.setPosition(getInnerX(), getInnerY());
- tabBoxClip.setSize(tabBoxWidth, getInnerHeight() - scrollCtrlsHeight);
- container.setSize(getInnerWidth() - tabBoxWidth, getInnerHeight());
+ tabBoxClip.setSize(tabBoxWidth, Math.max(0, getInnerHeight() - scrollCtrlsHeight));
+ container.setSize(Math.max(0, getInnerWidth() - tabBoxWidth), getInnerHeight());
container.setPosition(tabBoxClip.getRight(), getInnerY());
break;
case RIGHT:
tabBoxClip.setPosition(getInnerX() - tabBoxWidth, getInnerY());
- tabBoxClip.setSize(tabBoxWidth, getInnerHeight() - scrollCtrlsHeight);
- container.setSize(getInnerWidth() - tabBoxWidth, getInnerHeight());
+ tabBoxClip.setSize(tabBoxWidth, Math.max(0, getInnerHeight() - scrollCtrlsHeight));
+ container.setSize(Math.max(0, getInnerWidth() - tabBoxWidth), getInnerHeight());
container.setPosition(getInnerX(), getInnerY());
break;
case BOTTOM:
tabBoxClip.setPosition(getInnerX(), getInnerY() - tabBoxHeight);
- tabBoxClip.setSize(getInnerWidth() - scrollCtrlsWidth, tabBoxHeight);
- container.setSize(getInnerWidth(), getInnerHeight() - tabBoxHeight);
+ tabBoxClip.setSize(Math.max(0, getInnerWidth() - scrollCtrlsWidth), tabBoxHeight);
+ container.setSize(getInnerWidth(), Math.max(0, getInnerHeight() - tabBoxHeight));
container.setPosition(getInnerX(), getInnerY());
break;
}
@@ -435,12 +457,13 @@ private void validateTab(Tab tab) {
}
public class Tab extends HasCallback implements BooleanModel {
- final ToggleButton button;
+ final TabButton button;
Widget pane;
+ Runnable closeCallback;
+ Object userValue;
Tab() {
- button = new ToggleButton(this);
- button.setTheme("tabbutton");
+ button = new TabButton(this);
}
public boolean getValue() {
@@ -474,12 +497,46 @@ public Tab setTitle(String title) {
button.setText(title);
return this;
}
+
+ public String getTitle() {
+ return button.getText();
+ }
+ public Object getUserValue() {
+ return userValue;
+ }
+
+ public void setUserValue(Object userValue) {
+ this.userValue = userValue;
+ }
+
+ /**
+ * Sets the user theme for the tab button. If no user theme is set
+ * ({@code null}) then it will use "tabbutton" or
+ * "tabbuttonWithCloseButton" if a close callback is registered.
+ *
+ * @param theme the user theme name - can be null.
+ * @return {@code this}
+ */
public Tab setTheme(String theme) {
- button.setTheme(theme);
+ button.setUserTheme(theme);
return this;
}
+ public Runnable getCloseCallback() {
+ return closeCallback;
+ }
+
+ public void setCloseCallback(Runnable closeCallback) {
+ if(this.closeCallback != null) {
+ button.removeCloseButton();
+ }
+ this.closeCallback = closeCallback;
+ if(closeCallback != null) {
+ button.setCloseButton(closeCallback);
+ }
+ }
+
@Override
protected void doCallback() {
if(pane != null) {
@@ -489,39 +546,88 @@ protected void doCallback() {
}
}
- private static class Container extends Widget {
- @Override
- public int getMinWidth() {
- return Math.max(super.getMinWidth(), getBorderHorizontal() +
- BoxLayout.computeMinWidthVertical(this));
+ private static class TabButton extends ToggleButton {
+ Button closeButton;
+ Alignment closeButtonAlignment;
+ int closeButtonOffsetX;
+ int closeButtonOffsetY;
+ String userTheme;
+
+ TabButton(BooleanModel model) {
+ super(model);
+ setCanAcceptKeyboardFocus(false);
+ closeButtonAlignment = Alignment.RIGHT;
}
- @Override
- public int getMinHeight() {
- return Math.max(super.getMinHeight(), getBorderVertical() +
- BoxLayout.computeMinHeightHorizontal(this));
+ public void setUserTheme(String userTheme) {
+ this.userTheme = userTheme;
+ doSetTheme();
+ }
+
+ private void doSetTheme() {
+ if(userTheme != null) {
+ setTheme(userTheme);
+ } else if(closeButton != null) {
+ setTheme("tabbuttonWithCloseButton");
+ } else {
+ setTheme("tabbutton");
+ }
+ reapplyTheme();
}
@Override
- public int getPreferredInnerWidth() {
- return BoxLayout.computePreferredWidthVertical(this);
+ protected void applyTheme(ThemeInfo themeInfo) {
+ super.applyTheme(themeInfo);
+ if(closeButton != null) {
+ closeButtonAlignment = themeInfo.getParameter("closeButtonAlignment", Alignment.RIGHT);
+ closeButtonOffsetX = themeInfo.getParameter("closeButtonOffsetX", 0);
+ closeButtonOffsetY = themeInfo.getParameter("closeButtonOffsetY", 0);
+ } else {
+ closeButtonAlignment = Alignment.RIGHT;
+ closeButtonOffsetX = 0;
+ closeButtonOffsetY = 0;
+ }
+ }
+
+ void setCloseButton(Runnable callback) {
+ closeButton = new Button();
+ closeButton.setTheme("closeButton");
+ doSetTheme();
+ add(closeButton);
+ closeButton.addCallback(callback);
+ }
+
+ void removeCloseButton() {
+ removeChild(closeButton);
+ closeButton = null;
+ doSetTheme();
}
@Override
public int getPreferredInnerHeight() {
- return BoxLayout.computePreferredHeightHorizontal(this);
+ return computeTextHeight();
}
+ @Override
+ public int getPreferredInnerWidth() {
+ return computeTextWidth();
+ }
+
@Override
protected void layout() {
- layoutChildrenFullInnerArea();
+ if(closeButton != null) {
+ closeButton.adjustSize();
+ closeButton.setPosition(
+ getX() + closeButtonOffsetX + closeButtonAlignment.computePositionX(getWidth(), closeButton.getWidth()),
+ getY() + closeButtonOffsetY + closeButtonAlignment.computePositionY(getHeight(), closeButton.getHeight()));
+ }
}
}
private class CB implements Runnable {
final int dir;
- public CB(int dir) {
+ CB(int dir) {
this.dir = dir;
}
diff --git a/twl/src/de/matthiasmann/twl/TableBase.java b/twl/src/de/matthiasmann/twl/TableBase.java
index 5a68acf..086b5f4 100644
--- a/twl/src/de/matthiasmann/twl/TableBase.java
+++ b/twl/src/de/matthiasmann/twl/TableBase.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2008-2010, Matthias Mann
+ * Copyright (c) 2008-2012, Matthias Mann
*
* All rights reserved.
*
@@ -36,6 +36,7 @@
import de.matthiasmann.twl.renderer.AnimationState.StateKey;
import de.matthiasmann.twl.renderer.Image;
import de.matthiasmann.twl.renderer.MouseCursor;
+import de.matthiasmann.twl.renderer.Renderer;
import de.matthiasmann.twl.utils.CallbackSupport;
import de.matthiasmann.twl.utils.SizeSequence;
import de.matthiasmann.twl.utils.SparseGrid;
@@ -188,6 +189,7 @@ public interface DragListener {
public static final StateKey STATE_ROW_SELECTED = StateKey.get("rowSelected");
public static final StateKey STATE_ROW_HOVER = StateKey.get("rowHover");
public static final StateKey STATE_ROW_DROPTARGET = StateKey.get("rowDropTarget");
+ public static final StateKey STATE_ROW_ODD = StateKey.get("rowOdd");
public static final StateKey STATE_LEAD_ROW = StateKey.get("leadRow");
public static final StateKey STATE_SELECTED = StateKey.get("selected");
public static final StateKey STATE_SORT_ASCENDING = StateKey.get("sortAscending");
@@ -205,6 +207,7 @@ public interface DragListener {
protected SizeSequence rowModel;
protected boolean hasCellWidgetCreators;
protected ColumnHeader[] columnHeaders;
+ protected CellRenderer[] columnDefaultCellRenderer;
protected TableSelectionManager selectionManager;
protected KeyboardSearchHandler keyboardSearchHandler;
protected DragListener dragListener;
@@ -220,6 +223,7 @@ public interface DragListener {
protected MouseCursor columnResizeCursor;
protected MouseCursor normalCursor;
protected MouseCursor dragNotPossibleCursor;
+ protected boolean ensureColumnHeaderMinWidth;
protected int numRows;
protected int numColumns;
@@ -255,6 +259,7 @@ protected TableBase() {
this.removeCellWidgetsFunction = new RemoveCellWidgets();
this.insertCellWidgetsFunction = new InsertCellWidgets();
this.columnModel = new ColumnSizeSequence();
+ this.columnDefaultCellRenderer = new CellRenderer[8];
this.cellWidgetContainer = new CellWidgetContainer();
super.insertChild(cellWidgetContainer, 0);
@@ -494,11 +499,20 @@ public int getMinHeight() {
@Override
public int getPreferredInnerWidth() {
+ if(getInnerWidth() == 0) {
+ return columnModel.computePreferredWidth();
+ }
+ if(updateAllColumnWidth) {
+ updateAllColumnWidth();
+ }
return (numColumns > 0) ? getColumnEndPosition(numColumns-1) : 0;
}
@Override
public int getPreferredInnerHeight() {
+ if(autoSizeAllRows) {
+ autoSizeAllRows();
+ }
return columnHeaderHeight + 1 + // +1 for drop marker
((numRows > 0) ? getRowEndPosition(numRows-1) : 0);
}
@@ -621,6 +635,7 @@ protected void applyThemeTableBase(ThemeInfo themeInfo) {
this.defaultColumnWidth = themeInfo.getParameter("columnHeaderWidth", 256);
this.columnHeaderHeight = themeInfo.getParameter("columnHeaderHeight", 10);
this.columnDividerDragableDistance = themeInfo.getParameter("columnDividerDragableDistance", 3);
+ this.ensureColumnHeaderMinWidth = themeInfo.getParameter("ensureColumnHeaderMinWidth", false);
for(CellRenderer cellRenderer : cellRenderers.getUniqueValues()) {
applyCellRendererTheme(cellRenderer);
@@ -784,8 +799,9 @@ protected void paintWidget(GUI gui) {
final int innerHeight = getInnerHeight() - columnHeaderHeight;
final int offsetX = getOffsetX();
final int offsetY = getOffsetY();
+ final Renderer renderer = gui.getRenderer();
- gui.clipEnter(innerX, innerY, innerWidth, innerHeight);
+ renderer.clipEnter(innerX, innerY, innerWidth, innerHeight);
try {
final AnimationState animState = getAnimationState();
final int leadRow;
@@ -866,7 +882,7 @@ protected void paintWidget(GUI gui) {
imageRowDropMarker.draw(animState, getOffsetX(), getOffsetY() + y, columnModel.getEndPosition(), 1);
}
} finally {
- gui.clipLeave();
+ renderer.clipLeave();
}
}
@@ -887,6 +903,7 @@ private void paintRowImage(Image img, int leadRow) {
lastMouseY >= curY && lastMouseY < (curY + curRowHeight));
animState.setAnimationState(STATE_LEAD_ROW, row == leadRow);
animState.setAnimationState(STATE_ROW_DROPTARGET, !dropMarkerBeforeRow && row == dropMarkerRow);
+ animState.setAnimationState(STATE_ROW_ODD, (row & 1) == 1);
img.draw(animState, x, curY, width, curRowHeight);
rowStartPos = rowEndPos;
@@ -919,19 +936,60 @@ protected boolean isCellSelected(int row, int column) {
return false;
}
- protected CellRenderer getCellRenderer(Object data) {
+ /**
+ * Sets the default cell renderer for the specified column
+ * The column numbers are not affected by model changes.
+ *
+ * @param column the column, must eb >= 0
+ * @param cellRenderer the CellRenderer to use or null to restore the global default
+ */
+ public void setColumnDefaultCellRenderer(int column, CellRenderer cellRenderer) {
+ if(column >= columnDefaultCellRenderer.length) {
+ CellRenderer[] tmp = new CellRenderer[Math.max(column+1, numColumns)];
+ System.arraycopy(columnDefaultCellRenderer, 0, tmp, 0, columnDefaultCellRenderer.length);
+ columnDefaultCellRenderer = tmp;
+ }
+
+ columnDefaultCellRenderer[column] = cellRenderer;
+ }
+
+ /**
+ * Returns the default cell renderer for the specified column
+ * @param column the column, must eb >= 0
+ * @return the previously set CellRenderer or null if non was set
+ */
+ public CellRenderer getColumnDefaultCellRenderer(int column) {
+ if(column < columnDefaultCellRenderer.length) {
+ return columnDefaultCellRenderer[column];
+ }
+ return null;
+ }
+
+ protected CellRenderer getCellRendererNoDefault(Object data) {
Class extends Object> dataClass = data.getClass();
- CellRenderer cellRenderer = cellRenderers.get(dataClass);
+ return cellRenderers.get(dataClass);
+ }
+
+ protected CellRenderer getDefaultCellRenderer(int col) {
+ CellRenderer cellRenderer = getColumnDefaultCellRenderer(col);
if(cellRenderer == null) {
cellRenderer = stringCellRenderer;
}
return cellRenderer;
}
+
+ protected CellRenderer getCellRenderer(Object data, int col) {
+ CellRenderer cellRenderer = getCellRendererNoDefault(data);
+ if(cellRenderer == null) {
+ cellRenderer = getDefaultCellRenderer(col);
+ }
+ return cellRenderer;
+ }
protected CellRenderer getCellRenderer(int row, int col, TreeTableNode node) {
final Object data = getCellData(row, col, node);
if(data != null) {
- CellRenderer cellRenderer = getCellRenderer(data);
+ CellRenderer cellRenderer = getCellRenderer(data, col);
cellRenderer.setCellData(row, col, data);
return cellRenderer;
}
@@ -1231,7 +1289,7 @@ Widget routeMouseEvent(Event evt) {
if(lastMouseRow != -1 || lastMouseColumn != -1) {
lastMouseRow = -1;
lastMouseColumn = -1;
- updateTooltip();
+ resetTooltip();
}
} else {
final int row = getRowUnderMouse(evt.getMouseY());
@@ -1240,7 +1298,7 @@ Widget routeMouseEvent(Event evt) {
if(lastMouseRow != row || lastMouseColumn != column) {
lastMouseRow = row;
lastMouseColumn = column;
- updateTooltip();
+ resetTooltip();
}
}
}
@@ -1372,8 +1430,10 @@ protected void columnHeaderClicked(int column) {
}
protected void updateAllColumnWidth() {
- columnModel.initializeAll(numColumns);
- updateAllColumnWidth = false;
+ if(getInnerWidth() > 0) {
+ columnModel.initializeAll(numColumns);
+ updateAllColumnWidth = false;
+ }
}
protected void updateAll() {
@@ -1403,7 +1463,7 @@ protected void modelAllChanged() {
updateColumnHeader(i);
}
updateColumnHeaderNumbers();
-
+
if(selectionManager != null) {
selectionManager.modelChanged();
}
@@ -1595,14 +1655,20 @@ protected void initializeSizes(int index, int count) {
protected class ColumnSizeSequence extends SizeSequence {
@Override
protected void initializeSizes(int index, int count) {
- if(isFixedWidthMode()) {
- computeColumnHeaderLayout();
- for(int i=0 ; iThis callback is fired before {@link Callback#handleLinkClicked(java.lang.String) }
+ *
+ *
This callback should not modify the model - if this feature is required use
+ * {@link GUI#invokeLater(java.lang.Runnable) }.
+ *
+ * @param evt the mouse event
+ * @param element the element under the mouse
+ * @see Event.Type#MOUSE_BTNDOWN
+ * @see Event.Type#MOUSE_BTNUP
+ * @see Event.Type#MOUSE_CLICKED
+ */
+ public void handleMouseButton(Event evt, TextAreaModel.Element element);
+ }
public static final StateKey STATE_HOVER = StateKey.get("hover");
+ static final char[] EMPTY_CHAR_ARRAY = new char[0];
+
private final HashMap widgets;
private final HashMap widgetResolvers;
private final HashMap userImages;
@@ -95,10 +125,14 @@ public interface Callback {
private MouseCursor mouseCursorLink;
private DraggableButton.DragListener dragListener;
- final LClip layoutRoot;
- final ArrayList allBGImages;
+ private final LClip layoutRoot;
+ private final ArrayList allBGImages;
+ private final RenderInfo renderInfo;
private boolean inLayoutCode;
private boolean forceRelayout;
+ private Dimension preferredInnerSize;
+ private FontMapper fontMapper;
+ private FontMapperCacheEntry[] fontMapperCache;
private int lastMouseX;
private int lastMouseY;
@@ -106,7 +140,7 @@ public interface Callback {
private boolean dragging;
private int dragStartX;
private int dragStartY;
- LElement curLElementUnderMouse;
+ private LElement curLElementUnderMouse;
public TextArea() {
this.widgets = new HashMap();
@@ -114,8 +148,8 @@ public TextArea() {
this.userImages = new HashMap();
this.imageResolvers = new ArrayList();
this.layoutRoot = new LClip(null);
- this.layoutRoot.lineInfo = new char[0];
this.allBGImages = new ArrayList();
+ this.renderInfo = new RenderInfo(getAnimationState());
this.modelCB = new Runnable() {
public void run() {
@@ -292,6 +326,13 @@ protected void applyThemeTextArea(ThemeInfo themeInfo) {
forceRelayout();
}
+ @Override
+ protected void afterAddToGUI(GUI gui) {
+ super.afterAddToGUI(gui);
+ renderInfo.asNormal.setGUI(gui);
+ renderInfo.asHover.setGUI(gui);
+ }
+
@Override
public void insertChild(Widget child, int index) {
throw new UnsupportedOperationException("use registerWidget");
@@ -307,13 +348,61 @@ public Widget removeChild(int index) {
throw new UnsupportedOperationException("use registerWidget");
}
+ private void computePreferredInnerSize() {
+ int prefWidth = -1;
+ int prefHeight = -1;
+
+ if(model == null) {
+ prefWidth = 0;
+ prefHeight = 0;
+
+ } else if(getMaxWidth() > 0) {
+ final int borderHorizontal = getBorderHorizontal();
+ final int maxWidth = Math.max(0, getMaxWidth() - borderHorizontal);
+ final int minWidth = Math.max(0, getMinWidth() - borderHorizontal);
+
+ if(minWidth < maxWidth) {
+ //System.out.println("Doing preferred size computation");
+
+ LClip tmpRoot = new LClip(null);
+ startLayout();
+ try {
+ tmpRoot.width = maxWidth;
+ Box box = new Box(tmpRoot, 0, 0, 0, false);
+ layoutElements(box, model);
+ box.finish();
+
+ prefWidth = Math.max(0, maxWidth - box.minRemainingWidth);
+ prefHeight = box.curY;
+ } finally {
+ endLayout();
+ }
+ }
+ }
+ preferredInnerSize = new Dimension(prefWidth, prefHeight);
+ }
+
@Override
public int getPreferredInnerWidth() {
+ if(preferredInnerSize == null) {
+ computePreferredInnerSize();
+ }
+ if(preferredInnerSize.getX() >= 0) {
+ return preferredInnerSize.getX();
+ }
return getInnerWidth();
}
@Override
public int getPreferredInnerHeight() {
+ if(getInnerWidth() == 0) {
+ if(preferredInnerSize == null) {
+ computePreferredInnerSize();
+ }
+ if(preferredInnerSize.getY() >= 0) {
+ return preferredInnerSize.getY();
+ }
+ }
validateLayout();
return layoutRoot.height;
}
@@ -321,15 +410,13 @@ public int getPreferredInnerHeight() {
@Override
public int getPreferredWidth() {
int maxWidth = getMaxWidth();
- if(maxWidth > 0) {
- return maxWidth;
- }
return computeSize(getMinWidth(), super.getPreferredWidth(), maxWidth);
}
@Override
public void setMaxSize(int width, int height) {
if(width != getMaxWidth()) {
+ preferredInnerSize = null;
invalidateLayout();
}
super.setMaxSize(width, height);
@@ -338,6 +425,7 @@ public void setMaxSize(int width, int height) {
@Override
public void setMinSize(int width, int height) {
if(width != getMinWidth()) {
+ preferredInnerSize = null;
invalidateLayout();
}
super.setMinSize(width, height);
@@ -345,25 +433,21 @@ public void setMinSize(int width, int height) {
@Override
protected void layout() {
- int targetWidth = computeSize(getMinWidth(), getWidth(), getMaxWidth());
- targetWidth -= getBorderHorizontal();
+ int targetWidth = getInnerWidth();
- //System.out.println(this+" minWidth="+getMinWidth()+" width="+getWidth()+" maxWidth="+getMaxWidth());
+ //System.out.println(this+" minWidth="+getMinWidth()+" width="+getWidth()+" maxWidth="+getMaxWidth()+" targetWidth="+targetWidth+" preferredInnerSize="+preferredInnerSize);
// only recompute the layout when it has changed
if(layoutRoot.width != targetWidth || forceRelayout) {
layoutRoot.width = targetWidth;
- this.inLayoutCode = true;
- this.forceRelayout = false;
-
- if(styleClassResolver != null) {
- styleClassResolver.startLayout();
- }
+ inLayoutCode = true;
+ forceRelayout = false;
+ int requiredHeight;
- clearLayout();
- Box box = new Box(layoutRoot, 0, 0, 0);
-
+ startLayout();
try {
+ clearLayout();
+ Box box = new Box(layoutRoot, 0, 0, 0, true);
if(model != null) {
layoutElements(box, model);
@@ -371,20 +455,23 @@ protected void layout() {
// set position & size of all widget elements
layoutRoot.adjustWidget(getInnerX(), getInnerY());
+ layoutRoot.collectBGImages(0, 0, allBGImages);
}
updateMouseHover();
+ requiredHeight = box.curY;
} finally {
inLayoutCode = false;
+ endLayout();
}
- if(styleClassResolver != null) {
- styleClassResolver.layoutFinished();
- }
-
- if(layoutRoot.height != box.curY) {
- layoutRoot.height = box.curY;
- // call outside of inLayoutCode range
- invalidateLayout();
+ //System.out.println("layoutRoot.height="+layoutRoot.height+" box.curY="+box.curY+" remaining="+box.minRemainingWidth);
+
+ if(layoutRoot.height != requiredHeight) {
+ layoutRoot.height = requiredHeight;
+ if(getInnerHeight() != requiredHeight) {
+ // call outside of inLayoutCode range
+ invalidateLayout();
+ }
}
}
}
@@ -392,15 +479,16 @@ protected void layout() {
@Override
protected void paintWidget(GUI gui) {
final ArrayList bi = allBGImages;
- final int innerX = getInnerX();
- final int innerY = getInnerY();
- final AnimationState as = getAnimationState();
+ final RenderInfo ri = renderInfo;
+ ri.offsetX = getInnerX();
+ ri.offsetY = getInnerY();
+ ri.renderer = gui.getRenderer();
for(int i=0,n=bi.size() ; i elements) {
for(TextAreaModel.Element e : elements) {
@@ -698,9 +824,12 @@ private void layout(Box box, TextAreaModel.Element e, LElement le, TextAreaModel
le.y = box.computeTopPadding(le.marginTop);
box.computePadding();
} else if(display != TextAreaModel.Display.INLINE) {
+ box.accountMinRemaining(Math.max(0, box.lineWidth - le.width));
box.nextLine(false);
}
}
+
+ private static final int DEFAULT_FONT_SIZE = 14;
int convertToPX(Style style, StyleAttribute attribute, int full, int auto) {
style = style.resolve(attribute, styleClassResolver);
@@ -708,6 +837,12 @@ int convertToPX(Style style, StyleAttribute attribute, int full, int auto
Font font = null;
if(valueUnit.unit.isFontBased()) {
+ if(attribute == StyleAttribute.FONT_SIZE) {
+ style = style.getParent();
+ if(style == null) {
+ return DEFAULT_FONT_SIZE;
+ }
+ }
font = selectFont(style);
if(font == null) {
return 0;
@@ -725,6 +860,9 @@ int convertToPX(Style style, StyleAttribute attribute, int full, int auto
case PERCENT:
value *= full * 0.01f;
break;
+ case PT:
+ value *= 1.33f; // 96 DPI
+ break;
case AUTO:
return auto;
}
@@ -742,15 +880,131 @@ int convertToPX0(Style style, StyleAttribute attribute, int full) {
}
private Font selectFont(Style style) {
- String fontName = style.get(StyleAttribute.FONT_NAME, styleClassResolver);
- if(fontName != null && fonts != null) {
- Font font = fonts.getFont(fontName);
- if(font != null) {
- return font;
+ StringList fontFamilies = style.get(StyleAttribute.FONT_FAMILIES, styleClassResolver);
+ if(fontFamilies != null) {
+ if(fontMapper != null) {
+ Font font = selectFontMapper(style, fontMapper, fontFamilies);
+ if(font != null) {
+ return font;
+ }
+ }
+
+ if(fonts != null) {
+ do {
+ Font font = fonts.getFont(fontFamilies.getValue());
+ if(font != null) {
+ return font;
+ }
+ } while((fontFamilies=fontFamilies.getNext()) != null);
}
}
return defaultFont;
}
+
+ private static final StateSelect HOVER_STATESELECT =
+ new StateSelect(new StateExpression.Check(STATE_HOVER));
+
+ private static final int FONT_MAPPER_CACHE_SIZE = 16;
+
+ private static final class FontMapperCacheEntry {
+ final int fontSize;
+ final int fontStyle;
+ final StringList fontFamilies;
+ final TextDecoration tdNormal;
+ final TextDecoration tdHover;
+ final int hashCode;
+ final Font font;
+ FontMapperCacheEntry next;
+
+ FontMapperCacheEntry(int fontSize, int fontStyle, StringList fontFamilies, TextDecoration tdNormal, TextDecoration tdHover, int hashCode, Font font) {
+ this.fontSize = fontSize;
+ this.fontStyle = fontStyle;
+ this.fontFamilies = fontFamilies;
+ this.tdNormal = tdNormal;
+ this.tdHover = tdHover;
+ this.hashCode = hashCode;
+ this.font = font;
+ }
+ }
+
+ private Font selectFontMapper(Style style, FontMapper fontMapper, StringList fontFamilies) {
+ int fontSize = convertToPX(style, StyleAttribute.FONT_SIZE, DEFAULT_FONT_SIZE, DEFAULT_FONT_SIZE);
+ int fontStyle = 0;
+ if(style.get(StyleAttribute.FONT_WEIGHT, styleClassResolver) >= 550) {
+ fontStyle |= FontMapper.STYLE_BOLD;
+ }
+ if(style.get(StyleAttribute.FONT_ITALIC, styleClassResolver)) {
+ fontStyle |= FontMapper.STYLE_ITALIC;
+ }
+
+ TextDecoration textDecoration = style.get(StyleAttribute.TEXT_DECORATION, styleClassResolver);
+ TextDecoration textDecorationHover = style.get(StyleAttribute.TEXT_DECORATION_HOVER, styleClassResolver);
+
+ int hashCode = fontSize;
+ hashCode = hashCode * 67 + fontStyle;
+ hashCode = hashCode * 67 + fontFamilies.hashCode();
+ hashCode = hashCode * 67 + textDecoration.hashCode();
+ hashCode = hashCode * 67 + ((textDecorationHover != null) ? textDecorationHover.hashCode() : 0);
+
+ int cacheIdx = hashCode & (FONT_MAPPER_CACHE_SIZE-1);
+
+ if(fontMapperCache != null) {
+ for(FontMapperCacheEntry ce=fontMapperCache[cacheIdx] ; ce!=null ; ce=ce.next) {
+ if(ce.hashCode == hashCode &&
+ ce.fontSize == fontSize &&
+ ce.fontStyle == fontStyle &&
+ ce.tdNormal == textDecoration &&
+ ce.tdHover == textDecorationHover &&
+ ce.fontFamilies.equals(fontFamilies)) {
+ return ce.font;
+ }
+ }
+ } else {
+ fontMapperCache = new FontMapperCacheEntry[FONT_MAPPER_CACHE_SIZE];
+ }
+
+ FontParameter fpNormal = createFontParameter(textDecoration);
+
+ StateSelect select;
+ FontParameter[] params;
+
+ if(textDecorationHover != null) {
+ FontParameter fpHover = createFontParameter(textDecorationHover);
+
+ select = HOVER_STATESELECT;
+ params = new FontParameter[] { fpHover, fpNormal };
+ } else {
+ select = StateSelect.EMPTY;
+ params = new FontParameter[] { fpNormal };
+ }
+
+ Font font = fontMapper.getFont(fontFamilies, fontSize, fontStyle, select, params);
+
+ FontMapperCacheEntry ce = new FontMapperCacheEntry(fontSize, fontStyle,
+ fontFamilies, textDecoration, textDecorationHover, hashCode, font);
+ ce.next = fontMapperCache[cacheIdx];
+ fontMapperCache[cacheIdx] = ce;
+
+ return font;
+ }
+
+ private static FontParameter createFontParameter(TextDecoration deco) {
+ FontParameter fp = new FontParameter();
+ fp.put(FontParameter.UNDERLINE, deco == TextDecoration.UNDERLINE);
+ fp.put(FontParameter.LINETHROUGH, deco == TextDecoration.LINE_THROUGH);
+ return fp;
+ }
+
+ private FontData createFontData(Style style) {
+ Font font = selectFont(style);
+ if(font == null) {
+ return null;
+ }
+
+ return new FontData(font,
+ style.get(StyleAttribute.COLOR, styleClassResolver),
+ style.get(StyleAttribute.COLOR_HOVER, styleClassResolver));
+ }
private Image selectImage(Style style, StyleAttribute element) {
String imageName = style.get(element, styleClassResolver);
@@ -801,14 +1055,22 @@ private void layoutParagraphElement(Box box, TextAreaModel.ParagraphElement pe)
private void layoutTextElement(Box box, TextAreaModel.TextElement te) {
final String text = te.getText();
final Style style = te.getStyle();
- final Font font = selectFont(style);
+ final FontData fontData = createFontData(style);
final boolean pre = style.get(StyleAttribute.PREFORMATTED, styleClassResolver);
- if(font == null) {
+ if(fontData == null) {
return;
}
- box.setupTextParams(style, font, false);
+ final boolean inheritHover;
+ Boolean inheritHoverStyle = style.resolve(StyleAttribute.INHERIT_HOVER, styleClassResolver).getRaw(StyleAttribute.INHERIT_HOVER);
+ if(inheritHoverStyle != null) {
+ inheritHover = inheritHoverStyle.booleanValue();
+ } else {
+ inheritHover = (box.style != null) && (box.style == style.getParent());
+ }
+
+ box.setupTextParams(style, fontData.font, false);
if(pre && !box.wasPreformatted) {
box.nextLine(false);
@@ -818,9 +1080,9 @@ private void layoutTextElement(Box box, TextAreaModel.TextElement te) {
while(idx < text.length()) {
int end = TextUtil.indexOf(text, '\n', idx);
if(pre) {
- layoutTextPre(box, te, font, text, idx, end);
+ layoutTextPre(box, te, fontData, text, idx, end, inheritHover);
} else {
- layoutText(box, te, font, text, idx, end);
+ layoutText(box, te, fontData, text, idx, end, inheritHover);
}
if(end < text.length() && text.charAt(end) == '\n') {
@@ -833,8 +1095,8 @@ private void layoutTextElement(Box box, TextAreaModel.TextElement te) {
box.wasPreformatted = pre;
}
- private void layoutText(Box box, TextAreaModel.TextElement te, Font font,
- String text, int textStart, int textEnd) {
+ private void layoutText(Box box, TextAreaModel.TextElement te, FontData fontData,
+ String text, int textStart, int textEnd, boolean inheritHover) {
int idx = textStart;
// trim start
while(textStart < textEnd && isSkip(text.charAt(textStart))) {
@@ -847,6 +1109,8 @@ private void layoutText(Box box, TextAreaModel.TextElement te, Font font,
textEnd--;
}
+ Font font = fontData.font;
+
// check if we skipped white spaces and the previous element in this
// row was not a text cell
if(textStart > idx && box.prevOnLineEndsNotWithSpace()) {
@@ -922,7 +1186,7 @@ private void layoutText(Box box, TextAreaModel.TextElement te, Font font,
}
if(idx < end) {
- LText lt = new LText(te, font, text, idx, end);
+ LText lt = new LText(te, fontData, text, idx, end, box.doCacheText);
if(advancePastFloaters) {
box.advancePastFloaters(lt.width, box.marginLeft, box.marginRight);
}
@@ -938,6 +1202,7 @@ private void layoutText(Box box, TextAreaModel.TextElement te, Font font,
lt.x = box.getXAndAdvance(width);
lt.marginTop = (short)box.marginTop;
lt.href = box.href;
+ lt.inheritHover = inheritHover;
box.layout.add(lt);
}
@@ -953,14 +1218,15 @@ private void layoutText(Box box, TextAreaModel.TextElement te, Font font,
}
}
- private void layoutTextPre(Box box, TextAreaModel.TextElement te, Font font,
- String text, int textStart, int textEnd) {
+ private void layoutTextPre(Box box, TextAreaModel.TextElement te, FontData fontData,
+ String text, int textStart, int textEnd, boolean inheritHover) {
+ Font font = fontData.font;
int idx = textStart;
for(;;) {
while(idx < textEnd) {
if(text.charAt(idx) == '\t') {
idx++;
- int tabX = box.computeNextTabStop(font);
+ int tabX = box.computeNextTabStop(te.getStyle(), font);
if(tabX < box.lineWidth) {
box.curX = tabX;
} else if(!box.isAtStartOfLine()) {
@@ -982,9 +1248,10 @@ private void layoutTextPre(Box box, TextAreaModel.TextElement te, Font font,
end = idx + Math.max(1, count);
- LText lt = new LText(te, font, text, idx, end);
+ LText lt = new LText(te, fontData, text, idx, end, box.doCacheText);
lt.x = box.getXAndAdvance(lt.width);
lt.marginTop = (short)box.marginTop;
+ lt.inheritHover = inheritHover;
box.layout.add(lt);
}
@@ -1021,7 +1288,15 @@ private void layoutContainerElement(Box box, TextAreaModel.ContainerElement ce)
private void layoutLinkElement(Box box, TextAreaModel.LinkElement le) {
String oldHref = box.href;
box.href = le.getHREF();
- layoutContainerElement(box, le);
+
+ Style style = le.getStyle();
+ TextAreaModel.Display display = style.get(StyleAttribute.DISPLAY, styleClassResolver);
+ if(display == TextAreaModel.Display.BLOCK) {
+ layoutBlockElement(box, le);
+ } else {
+ layoutContainerElement(box, le);
+ }
+
box.href = oldHref;
}
@@ -1033,7 +1308,7 @@ private void layoutListElement(Box box, TextAreaModel.ListElement le) {
Image image = selectImage(style, StyleAttribute.LIST_STYLE_IMAGE);
if(image != null) {
LImage li = new LImage(le, image);
- li.width += convertToPX0(style, StyleAttribute.PADDING_LEFT, box.boxWidth);
+ li.marginRight = (short)convertToPX0(style, StyleAttribute.PADDING_LEFT, box.boxWidth);
layout(box, le, li, TextAreaModel.FloatPosition.LEFT, TextAreaModel.Display.BLOCK);
int imageHeight = li.height;
@@ -1056,9 +1331,9 @@ private void layoutListElement(Box box, TextAreaModel.ListElement le) {
private void layoutOrderedListElement(Box box, TextAreaModel.OrderedListElement ole) {
Style style = ole.getStyle();
- Font font = selectFont(style);
+ FontData fontData = createFontData(style);
- if(font == null) {
+ if(fontData == null) {
return;
}
@@ -1073,7 +1348,7 @@ private void layoutOrderedListElement(Box box, TextAreaModel.OrderedListElement
int maxLabelWidth = convertToPX0(style, StyleAttribute.PADDING_LEFT, box.boxWidth);
for(int i=0 ; i contentHeight) {
+ int amount = 0;
+ switch(style.get(StyleAttribute.VERTICAL_ALIGNMENT, styleClassResolver)) {
+ case BOTTOM:
+ amount = boxHeight - contentHeight;
+ break;
+
+ case FILL:
+ case MIDDLE:
+ amount = (boxHeight - contentHeight)/2;
+ break;
+ }
+ if(amount > 0) {
+ clip.moveContentY(amount);
+ }
+ }
+
+ clip.height = boxHeight;
clip.marginBottom = (short)Math.max(marginBottom, box.marginBottomAbs - box.curY);
return box;
}
- private void layoutBlockElement(Box box, TextAreaModel.BlockElement be) {
+ private void layoutBlockElement(Box box, TextAreaModel.ContainerElement be) {
box.nextLine(false);
final Style style = be.getStyle();
@@ -1140,17 +1436,25 @@ private void layoutBlockElement(Box box, TextAreaModel.BlockElement be) {
int bgWidth;
int remaining = Math.max(0, box.computeRightPadding(marginRight) - bgX);
+ int paddingLeft = convertToPX0(style, StyleAttribute.PADDING_LEFT, box.boxWidth);
+ int paddingRight = convertToPX0(style, StyleAttribute.PADDING_RIGHT, box.boxWidth);
if(floatPosition == TextAreaModel.FloatPosition.NONE) {
- bgWidth = remaining;
+ bgWidth = convertToPX(style, StyleAttribute.WIDTH, remaining, remaining);
} else {
- bgWidth = convertToPX(style, StyleAttribute.WIDTH, box.boxWidth, box.lineWidth);
- }
-
- int paddingLeft = convertToPX0(style, StyleAttribute.PADDING_LEFT, bgWidth);
- int paddingRight = convertToPX0(style, StyleAttribute.PADDING_RIGHT, bgWidth);
+ bgWidth = convertToPX(style, StyleAttribute.WIDTH, box.boxWidth, Integer.MIN_VALUE);
+ if(bgWidth == Integer.MIN_VALUE) {
+ LClip dummy = new LClip(null);
+ dummy.width = Math.max(0, box.lineWidth - paddingLeft - paddingRight);
- bgWidth += paddingLeft + paddingRight;
+ Box dummyBox = layoutBox(dummy, box.boxWidth, paddingLeft, paddingRight, be, null, false);
+ dummyBox.nextLine(false);
+
+ bgWidth = Math.max(0, dummy.width - dummyBox.minRemainingWidth);
+ }
+ }
+
+ bgWidth = Math.max(0, bgWidth) + paddingLeft + paddingRight;
if(floatPosition != TextAreaModel.FloatPosition.NONE) {
box.advancePastFloaters(bgWidth, marginLeft, marginRight);
@@ -1160,7 +1464,7 @@ private void layoutBlockElement(Box box, TextAreaModel.BlockElement be) {
remaining = Math.max(0, box.computeRightPadding(marginRight) - bgX);
}
- bgWidth = Math.max(0, Math.min(bgWidth, remaining));
+ bgWidth = Math.min(bgWidth, remaining);
if(floatPosition == TextAreaModel.FloatPosition.RIGHT) {
bgX = box.computeRightPadding(marginRight) - bgWidth;
@@ -1172,9 +1476,10 @@ private void layoutBlockElement(Box box, TextAreaModel.BlockElement be) {
clip.width = bgWidth;
clip.marginLeft = (short)marginLeft;
clip.marginRight = (short)marginRight;
+ clip.href = box.href;
box.layout.add(clip);
- layoutBox(clip, box.boxWidth, paddingLeft, paddingRight, be);
+ Box clipBox = layoutBox(clip, box.boxWidth, paddingLeft, paddingRight, be, box.href, box.doCacheText);
// sync main box with layout
box.lineStartIdx = box.layout.size();
@@ -1182,6 +1487,7 @@ private void layoutBlockElement(Box box, TextAreaModel.BlockElement be) {
if(floatPosition == TextAreaModel.FloatPosition.NONE) {
box.advanceToY(bgY + clip.height);
box.setMarginBottom(clip.marginBottom);
+ box.accountMinRemaining(clipBox.minRemainingWidth);
} else {
if(floatPosition == TextAreaModel.FloatPosition.RIGHT) {
box.objRight.add(clip);
@@ -1196,9 +1502,97 @@ private void layoutBlockElement(Box box, TextAreaModel.BlockElement be) {
bgImage.y = bgY;
bgImage.width = bgWidth;
bgImage.height = clip.height;
+ bgImage.hoverSrc = clip;
}
}
+ private void computeTableWidth(TextAreaModel.TableElement te,
+ int maxTableWidth, int columnWidth[], int columnSpacing[], boolean[] columnsWithFixedWidth) {
+ final int numColumns = te.getNumColumns();
+ final int numRows = te.getNumRows();
+ final int cellSpacing = te.getCellSpacing();
+ final int cellPadding = te.getCellPadding();
+
+ HashMap colspanWidths = null;
+
+ for(int col=0 ; col 1 || !hasFixedWidth)) {
+ int paddingLeft = Math.max(cellPadding, convertToPX0(cellStyle, StyleAttribute.PADDING_LEFT, maxTableWidth));
+ int paddingRight = Math.max(cellPadding, convertToPX0(cellStyle, StyleAttribute.PADDING_RIGHT, maxTableWidth));
+
+ LClip dummy = new LClip(null);
+ dummy.width = maxTableWidth;
+ Box dummyBox = layoutBox(dummy, maxTableWidth, paddingLeft, paddingRight, cell, null, false);
+ dummyBox.finish();
+
+ cellWidth = maxTableWidth - dummyBox.minRemainingWidth;
+ } else if(colspan == 1 && cellWidth >= 0) {
+ hasFixedWidth = true;
+ }
+
+ if(colspan > 1) {
+ if(colspanWidths == null) {
+ colspanWidths = new HashMap();
+ }
+ Integer key = (col << 16) + colspan;
+ Integer value = colspanWidths.get(key);
+ if(value == null || cellWidth > value) {
+ colspanWidths.put(key, cellWidth);
+ }
+ } else {
+ width = Math.max(width, cellWidth);
+ marginLeft = Math.max(marginLeft, convertToPX(cellStyle, StyleAttribute.MARGIN_LEFT, maxTableWidth, 0));
+ marginRight = Math.max(marginRight, convertToPX(cellStyle, StyleAttribute.MARGIN_LEFT, maxTableWidth, 0));
+ }
+ }
+ }
+
+ columnsWithFixedWidth[col] = hasFixedWidth;
+ columnWidth[col] = width;
+ columnSpacing[col ] = Math.max(columnSpacing[col], marginLeft);
+ columnSpacing[col+1] = Math.max(cellSpacing, marginRight);
+ }
+
+ if(colspanWidths != null) {
+ for(Map.Entry e : colspanWidths.entrySet()) {
+ int key = e.getKey();
+ int col = key >>> 16;
+ int colspan = key & 0xFFFF;
+ int width = e.getValue();
+ int remainingCols = colspan;
+
+ for(int i=0 ; i 0) {
+ for(int i=0 ; i 0 ; i++) {
+ if(!columnsWithFixedWidth[col+i]) {
+ int colWidth = width / remainingCols;
+ columnWidth[col+i] = Math.max(columnWidth[col+i], colWidth);
+ width -= colWidth;
+ remainingCols--;
+ }
+ }
+ }
+ }
+ }
+ }
+
private void layoutTableElement(Box box, TextAreaModel.TableElement te) {
final int numColumns = te.getNumColumns();
final int numRows = te.getNumRows();
@@ -1215,73 +1609,70 @@ private void layoutTableElement(Box box, TextAreaModel.TableElement te) {
int left = box.computeLeftPadding(convertToPX0(tableStyle, StyleAttribute.MARGIN_LEFT, box.boxWidth));
int right = box.computeRightPadding(convertToPX0(tableStyle, StyleAttribute.MARGIN_RIGHT, box.boxWidth));
- int tableWidth = Math.min(right - left, convertToPX0(tableStyle, StyleAttribute.WIDTH, box.boxWidth));
+ int maxTableWidth = Math.max(0, right - left);
+ int tableWidth = Math.min(maxTableWidth, convertToPX(tableStyle, StyleAttribute.WIDTH, box.boxWidth, Integer.MIN_VALUE));
+ boolean autoTableWidth = tableWidth == Integer.MIN_VALUE;
if(tableWidth <= 0) {
- tableWidth = Math.max(0, right - left);
+ tableWidth = maxTableWidth;
}
-
+
int columnWidth[] = new int[numColumns];
int columnSpacing[] = new int[numColumns + 1];
- int columnWidthSum = 0;
- int columnsWithoutWidth = 0;
-
+ boolean[] columnsWithFixedWidth = new boolean[numColumns];
+
columnSpacing[0] = Math.max(cellSpacing, convertToPX0(tableStyle, StyleAttribute.PADDING_LEFT, box.boxWidth));
- for(int col=0 ; col 0) {
- int remainingWidth = Math.max(0, tableWidth - columnSpacingSum - columnWidthSum);
- for(int col=0 ; col 0) {
int available = availableColumnWidth;
int toDistribute = columnWidthSum;
-
+ int remainingCols = numColumns;
+
for(int col=0 ; col 0) ? width * available / toDistribute : 0;
- columnWidth[col] = newWidth;
- available -= newWidth;
- toDistribute -= width;
+ if(columnsWithFixedWidth[col]) {
+ int width = columnWidth[col];
+ available -= width;
+ toDistribute -= width;
+ remainingCols--;
+ }
+ }
+
+ boolean allColumns = false;
+ if(availableColumnWidth < 0) {
+ available = availableColumnWidth;
+ toDistribute = columnWidthSum;
+ remainingCols = numColumns;
+ allColumns = true;
+ }
+
+ for(int col=0 ; col 0 ; col++) {
+ if(allColumns || !columnsWithFixedWidth[col]) {
+ int width = columnWidth[col];
+ int newWidth = (toDistribute > 0) ? width * available / toDistribute : 0;
+ columnWidth[col] = newWidth;
+ available -= newWidth;
+ toDistribute -= width;
+ }
}
}
@@ -1331,21 +1722,22 @@ private void layoutTableElement(Box box, TextAreaModel.TableElement te) {
int paddingLeft = Math.max(cellPadding, convertToPX0(cellStyle, StyleAttribute.PADDING_LEFT, tableWidth));
int paddingRight = Math.max(cellPadding, convertToPX0(cellStyle, StyleAttribute.PADDING_RIGHT, tableWidth));
+ LClip clip = new LClip(cell);
LImage bgImage = createBGImage(box, cell);
if(bgImage != null) {
bgImage.x = x;
bgImage.width = width;
+ bgImage.hoverSrc = clip;
bgImages[col] = bgImage;
}
- LClip clip = new LClip(cell);
clip.x = x;
clip.y = box.curY;
clip.width = width;
clip.marginTop = (short)convertToPX0(cellStyle, StyleAttribute.MARGIN_TOP, tableWidth);
box.layout.add(clip);
- layoutBox(clip, tableWidth, paddingLeft, paddingRight, cell);
+ layoutBox(clip, tableWidth, paddingLeft, paddingRight, cell, null, box.doCacheText);
col += Math.max(0, cell.getColspan()-1);
}
@@ -1374,6 +1766,7 @@ private void layoutTableElement(Box box, TextAreaModel.TableElement te) {
box.curY += Math.max(cellSpacing, convertToPX0(tableStyle, StyleAttribute.PADDING_BOTTOM, box.boxWidth));
box.checkFloaters();
+ box.accountMinRemaining(Math.max(0, box.lineWidth - tableWidth));
if(tableBGImage != null) {
tableBGImage.height = box.curY - tableBGImage.y;
@@ -1390,7 +1783,11 @@ private void layoutTableElement(Box box, TextAreaModel.TableElement te) {
}
private LImage createBGImage(Box box, TextAreaModel.Element element) {
- Image image = selectImage(element.getStyle(), StyleAttribute.BACKGROUND_IMAGE);
+ Style style = element.getStyle();
+ Image image = selectImage(style, StyleAttribute.BACKGROUND_IMAGE);
+ if(image == null) {
+ image = createBackgroundColor(style);
+ }
if(image != null) {
LImage bgImage = new LImage(element, image);
bgImage.y = box.curY;
@@ -1399,6 +1796,23 @@ private LImage createBGImage(Box box, TextAreaModel.Element element) {
}
return null;
}
+
+ private Image createBackgroundColor(Style style) {
+ Color color = style.get(StyleAttribute.BACKGROUND_COLOR, styleClassResolver);
+ if(color.getAlpha() != 0) {
+ Image white = selectImage("white");
+ if(white != null) {
+ Image image = white.createTintedVersion(color);
+ Color colorHover = style.get(StyleAttribute.BACKGROUND_COLOR_HOVER, styleClassResolver);
+ if(colorHover != null) {
+ return new StateSelectImage(HOVER_STATESELECT, null,
+ white.createTintedVersion(colorHover), image);
+ }
+ return image;
+ }
+ }
+ return null;
+ }
static boolean isSkip(char ch) {
return Character.isWhitespace(ch);
@@ -1409,7 +1823,8 @@ static boolean isPunctuation(char ch) {
}
static boolean isBreak(char ch) {
- return Character.isWhitespace(ch) || isPunctuation(ch);
+ return Character.isWhitespace(ch) || isPunctuation(ch) ||
+ (ch == 0x3001) || (ch == 0x3002);
}
class Box {
@@ -1422,6 +1837,7 @@ class Box {
final int boxWidth;
final int boxMarginOffsetLeft;
final int boxMarginOffsetRight;
+ final boolean doCacheText;
int curY;
int curX;
int lineStartIdx;
@@ -1437,23 +1853,27 @@ class Box {
int minLineHeight;
int lastLineEnd;
int lastLineBottom;
+ int minRemainingWidth;
boolean inParagraph;
boolean wasAutoBreak;
boolean wasPreformatted;
TextAreaModel.HAlignment textAlignment;
String href;
+ Style style;
- public Box(LClip clip, int paddingLeft, int paddingRight, int paddingTop) {
+ Box(LClip clip, int paddingLeft, int paddingRight, int paddingTop, boolean doCacheText) {
this.clip = clip;
this.layout = clip.layout;
this.boxLeft = paddingLeft;
this.boxWidth = Math.max(0, clip.width - paddingLeft - paddingRight);
this.boxMarginOffsetLeft = paddingLeft;
this.boxMarginOffsetRight = paddingRight;
- this.curX = this.boxLeft;
+ this.doCacheText = doCacheText;
+ this.curX = paddingLeft;
this.curY = paddingTop;
- this.lineStartX = boxLeft;
+ this.lineStartX = paddingLeft;
this.lineWidth = boxWidth;
+ this.minRemainingWidth = boxWidth;
this.textAlignment = TextAreaModel.HAlignment.LEFT;
assert layout.isEmpty();
}
@@ -1468,6 +1888,8 @@ void computePadding() {
if(isAtStartOfLine()) {
curX = lineStartX;
}
+
+ accountMinRemaining(getRemaining());
}
int computeLeftPadding(int marginLeft) {
@@ -1511,6 +1933,10 @@ void setMarginBottom(int marginBottom) {
int getRemaining() {
return Math.max(0, lineWidth - curX + lineStartX);
}
+
+ void accountMinRemaining(int remaining) {
+ minRemainingWidth = Math.min(minRemainingWidth, remaining);
+ }
int getXAndAdvance(int amount) {
int x = curX;
@@ -1602,6 +2028,8 @@ boolean nextLine(boolean force) {
return false;
}
+ accountMinRemaining(getRemaining());
+
int targetY = curY;
int lineHeight = minLineHeight;
@@ -1694,10 +2122,16 @@ void finish() {
lineInfo.getChars(0, lineInfoLength, clip.lineInfo, 0);
}
- int computeNextTabStop(Font font) {
+ int computeNextTabStop(Style style, Font font) {
+ int em = font.getEM();
+ int tabSize = style.get(StyleAttribute.TAB_SIZE, styleClassResolver);
+ if(tabSize <= 0 || em <= 0) {
+ // replace with single space when tabs are disabled
+ return curX + font.getSpaceWidth();
+ }
+ int tabSizePX = Math.min(tabSize, Short.MAX_VALUE / em) * em;
int x = curX - lineStartX + font.getSpaceWidth();
- int tabSize = 8 * font.getEM();
- return curX + tabSize - (x % tabSize);
+ return curX + tabSizePX - (x % tabSizePX);
}
private void removeObjFromList(ArrayList list) {
@@ -1727,9 +2161,9 @@ void setupTextParams(Style style, Font font, boolean isParagraphStart) {
marginRight = convertToPX0(style, StyleAttribute.MARGIN_RIGHT, boxWidth);
textAlignment = style.get(StyleAttribute.HORIZONTAL_ALIGNMENT, styleClassResolver);
computePadding();
- curX = Math.max(0, lineStartX + convertToPX(style, StyleAttribute.TEXT_IDENT, boxWidth, 0));
+ curX = Math.max(0, lineStartX + convertToPX(style, StyleAttribute.TEXT_INDENT, boxWidth, 0));
}
-
+
marginTop = convertToPX0(style, StyleAttribute.MARGIN_TOP, boxWidth);
}
@@ -1762,6 +2196,25 @@ private void processAnchors(int y, int height) {
}
}
+ static class RenderInfo {
+ int offsetX;
+ int offsetY;
+ Renderer renderer;
+ final AnimationState asNormal;
+ final AnimationState asHover;
+
+ RenderInfo(AnimationState parent) {
+ asNormal = new AnimationState(parent);
+ asNormal.setAnimationState(STATE_HOVER, false);
+ asHover = new AnimationState(parent);
+ asHover.setAnimationState(STATE_HOVER, true);
+ }
+
+ AnimationState getAnimationState(boolean isHover) {
+ return isHover ? asHover : asNormal;
+ }
+ }
+
static class LElement {
final TextAreaModel.Element element;
int x;
@@ -1773,13 +2226,16 @@ static class LElement {
short marginRight;
short marginBottom;
String href;
+ boolean isHover;
+ boolean inheritHover;
- public LElement(TextAreaModel.Element element) {
+ LElement(TextAreaModel.Element element) {
this.element = element;
}
void adjustWidget(int offX, int offY) {}
- void draw(int offX, int offY, AnimationState as) {}
+ void collectBGImages(int offX, int offY, ArrayList allBGImages) {}
+ void draw(RenderInfo ri) {}
void destroy() {}
boolean isInside(int x, int y) {
@@ -1795,26 +2251,54 @@ LElement find(TextAreaModel.Element element, int[] offset) {
}
return null;
}
+ boolean setHover(LElement le) {
+ isHover = (this == le) || (le != null && element == le.element);
+ return isHover;
+ }
int bottom() {
return y + height + marginBottom;
}
}
+
+ static class FontData {
+ final Font font;
+ final Color color;
+ final Color colorHover;
+
+ FontData(Font font, Color color, Color colorHover) {
+ if(colorHover == null) {
+ colorHover = color;
+ }
+ this.font = font;
+ this.color = maskWhite(color);
+ this.colorHover = maskWhite(colorHover);
+ }
+
+ public Color getColor(boolean isHover) {
+ return isHover ? colorHover : color;
+ }
+
+ private static Color maskWhite(Color c) {
+ return Color.WHITE.equals(c) ? null : c;
+ }
+ }
static class LText extends LElement {
- final Font font;
+ final FontData fontData;
final String text;
final int start;
final int end;
FontCache cache;
- public LText(TextAreaModel.Element element, Font font, String text, int start, int end) {
+ LText(TextAreaModel.Element element, FontData fontData, String text, int start, int end, boolean doCache) {
super(element);
- this.font = font;
+ Font font = fontData.font;
+ this.fontData = fontData;
this.text = text;
this.start = start;
this.end = end;
- this.cache = font.cacheText(null, text, start, end);
+ this.cache = doCache ? font.cacheText(null, text, start, end) : null;
this.height = font.getLineHeight();
if(cache != null) {
@@ -1825,11 +2309,27 @@ public LText(TextAreaModel.Element element, Font font, String text, int start, i
}
@Override
- void draw(int offX, int offY, AnimationState as) {
+ void draw(RenderInfo ri) {
+ Color c = fontData.getColor(isHover);
+ if(c != null) {
+ drawTextWithColor(ri, c);
+ } else {
+ drawText(ri);
+ }
+ }
+
+ private void drawTextWithColor(RenderInfo ri, Color c) {
+ ri.renderer.pushGlobalTintColor(c.getRedFloat(), c.getGreenFloat(), c.getBlueFloat(), c.getAlphaFloat());
+ drawText(ri);
+ ri.renderer.popGlobalTintColor();
+ }
+
+ private void drawText(RenderInfo ri) {
+ AnimationState as = ri.getAnimationState(isHover);
if(cache != null) {
- cache.draw(as, x+offX, y+offY);
+ cache.draw(as, x+ri.offsetX, y+ri.offsetY);
} else {
- font.drawText(as, x+offX, y+offY, text, start, end);
+ fontData.font.drawText(as, x+ri.offsetX, y+ri.offsetY, text, start, end);
}
}
@@ -1845,7 +2345,7 @@ void destroy() {
static class LWidget extends LElement {
final Widget widget;
- public LWidget(TextAreaModel.Element element, Widget widget) {
+ LWidget(TextAreaModel.Element element, Widget widget) {
super(element);
this.widget = widget;
}
@@ -1859,63 +2359,57 @@ void adjustWidget(int offX, int offY) {
static class LImage extends LElement {
final Image img;
+ LElement hoverSrc;
- public LImage(TextAreaModel.Element element, Image img) {
+ @SuppressWarnings("LeakingThisInConstructor")
+ LImage(TextAreaModel.Element element, Image img) {
super(element);
this.img = img;
this.width = img.getWidth();
this.height = img.getHeight();
+ this.hoverSrc = this;
}
@Override
- void draw(int offX, int offY, AnimationState as) {
- img.draw(as, x+offX, y+offY, width, height);
+ void draw(RenderInfo ri) {
+ img.draw(ri.getAnimationState(hoverSrc.isHover),
+ x+ri.offsetX, y+ri.offsetY, width, height);
}
}
- class LClip extends LElement {
+ static class LClip extends LElement {
final ArrayList layout;
final ArrayList bgImages;
final ArrayList anchors;
char[] lineInfo;
- public LClip(TextAreaModel.Element element) {
+ LClip(TextAreaModel.Element element) {
super(element);
this.layout = new ArrayList();
this.bgImages = new ArrayList();
this.anchors = new ArrayList();
+ this.lineInfo = EMPTY_CHAR_ARRAY;
}
-
+
@Override
- void draw(int offX, int offY, AnimationState as) {
- offX += x;
- offY += y;
- GUI gui = getGUI();
- gui.clipEnter(offX, offY, width, height);
+ void draw(RenderInfo ri) {
+ ri.offsetX += x;
+ ri.offsetY += y;
+ ri.renderer.clipEnter(ri.offsetX, ri.offsetY, width, height);
try {
- if(!gui.clipEmpty()) {
- drawNoClip(offX, offY, as);
+ if(!ri.renderer.clipIsEmpty()) {
+ final ArrayList ll = layout;
+ for(int i=0,n=ll.size() ; i ll = layout;
- final TextAreaModel.Element hoverElement;
- if(curLElementUnderMouse != null) {
- hoverElement = curLElementUnderMouse.element;
- } else {
- hoverElement = null;
- }
- for(int i=0,n=ll.size() ; i allBGImages) {
+ offX += x;
+ offY += y;
for(int i=0,n=bgImages.size() ; i l, TextAreaModel.Element e, int[] offs
}
return null;
}
+
+ @Override
+ boolean setHover(LElement le) {
+ boolean childHover = false;
+ for(int i=0,n=layout.size() ; i 0) {
+ if(lineInfo[1] == 0) {
+ lineInfo[0] += amount;
+ } else {
+ int n = lineInfo.length;
+ char[] tmpLineInfo = new char[n+2];
+ tmpLineInfo[0] = (char)amount;
+ for(int i=0 ; i 0) {
+ lineBottom += amount;
+ }
+ tmpLineInfo[i+2] = (char)lineBottom;
+ tmpLineInfo[i+3] = lineInfo[i+1];
+ }
+ lineInfo = tmpLineInfo;
+ }
+ }
+ }
}
}
diff --git a/twl/src/de/matthiasmann/twl/TextWidget.java b/twl/src/de/matthiasmann/twl/TextWidget.java
index 806f3db..a21b27c 100644
--- a/twl/src/de/matthiasmann/twl/TextWidget.java
+++ b/twl/src/de/matthiasmann/twl/TextWidget.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2008-2010, Matthias Mann
+ * Copyright (c) 2008-2012, Matthias Mann
*
* All rights reserved.
*
@@ -205,14 +205,18 @@ protected void paintLabelText(de.matthiasmann.twl.renderer.AnimationState animSt
if(hasText() && font != null) {
int x = computeTextX();
int y = computeTextY();
+
+ paintTextAt(animState, x, y);
+ }
+ }
- if(cache != null) {
- cache.draw(animState, x, y);
- } else if(numTextLines > 1) {
- font.drawMultiLineText(animState, x, y, text, computeTextWidth(), alignment.fontHAlignment);
- } else {
- font.drawText(animState, x, y, text);
- }
+ protected void paintTextAt(de.matthiasmann.twl.renderer.AnimationState animState, int x, int y) {
+ if(cache != null) {
+ cache.draw(animState, x, y);
+ } else if(numTextLines > 1) {
+ font.drawMultiLineText(animState, x, y, text, computeTextWidth(), alignment.fontHAlignment);
+ } else {
+ font.drawText(animState, x, y, text);
}
}
diff --git a/twl/src/de/matthiasmann/twl/TreePathDisplay.java b/twl/src/de/matthiasmann/twl/TreePathDisplay.java
index 064cf1b..1df10f2 100644
--- a/twl/src/de/matthiasmann/twl/TreePathDisplay.java
+++ b/twl/src/de/matthiasmann/twl/TreePathDisplay.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2008-2009, Matthias Mann
+ * Copyright (c) 2008-2012, Matthias Mann
*
* All rights reserved.
*
@@ -259,7 +259,7 @@ protected void doCallback(int key) {
switch(key) {
case Event.KEY_RETURN:
- if(fireResolvePath(getEditField().getText())) {
+ if(fireResolvePath(getText())) {
endEdit();
}
break;
diff --git a/twl/src/de/matthiasmann/twl/TreeTable.java b/twl/src/de/matthiasmann/twl/TreeTable.java
index adc893f..07bf613 100644
--- a/twl/src/de/matthiasmann/twl/TreeTable.java
+++ b/twl/src/de/matthiasmann/twl/TreeTable.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2008-2009, Matthias Mann
+ * Copyright (c) 2008-2012, Matthias Mann
*
* All rights reserved.
*
@@ -30,7 +30,6 @@
package de.matthiasmann.twl;
import de.matthiasmann.twl.model.BooleanModel;
-import de.matthiasmann.twl.model.ToggleButtonModel;
import de.matthiasmann.twl.model.TreeTableModel;
import de.matthiasmann.twl.model.TreeTableNode;
import de.matthiasmann.twl.utils.CallbackSupport;
@@ -49,6 +48,11 @@
*/
public class TreeTable extends TableBase {
+ public interface ExpandListener {
+ public void nodeExpanded(int row, TreeTableNode node);
+ public void nodeCollapsed(int row, TreeTableNode node);
+ }
+
private final ModelChangeListener modelChangeListener;
private final TreeLeafCellRenderer leafRenderer;
private final TreeNodeCellRenderer nodeRenderer;
@@ -57,6 +61,7 @@ public class TreeTable extends TableBase {
private int nodeStateTableSize;
TreeTableModel model;
private NodeState rootNodeState;
+ private ExpandListener[] expandListeners;
@SuppressWarnings("LeakingThisInConstructor")
public TreeTable() {
@@ -102,6 +107,14 @@ public void setModel(TreeTableModel model) {
invalidateLayout();
}
+ public void addExpandListener(ExpandListener listener) {
+ expandListeners = CallbackSupport.addCallbackToList(expandListeners, listener, ExpandListener.class);
+ }
+
+ public void removeExpandListener(ExpandListener listener) {
+ expandListeners = CallbackSupport.removeCallbackFromList(expandListeners, listener);
+ }
+
@Override
protected void applyTheme(ThemeInfo themeInfo) {
super.applyTheme(themeInfo);
@@ -277,6 +290,16 @@ protected void expandedChanged(NodeState ns) {
scrollPane.scrollToAreaY(rowStart, height, rowHeight/2);
}
}
+
+ if(expandListeners != null) {
+ for(ExpandListener el : expandListeners) {
+ if(ns.expanded) {
+ el.nodeExpanded(row, ns.key);
+ } else {
+ el.nodeCollapsed(row, ns.key);
+ }
+ }
+ }
}
protected int computeNumRows() {
@@ -453,6 +476,7 @@ protected class NodeState extends HashEntry implements
Runnable[] callbacks;
int level;
+ @SuppressWarnings("LeakingThisInConstructor")
public NodeState(TreeTableNode key, NodeState parent) {
super(key);
this.parent = parent;
@@ -515,7 +539,7 @@ static int getLevel(TreeTableNode node) {
return level;
}
- class TreeLeafCellRenderer implements CellRenderer {
+ class TreeLeafCellRenderer implements CellRenderer, CellWidgetCreator {
protected int treeIndent;
protected int level;
protected Dimension treeButtonSize = new Dimension(5, 5);
@@ -540,17 +564,17 @@ public void setCellData(int row, int column, Object data) {
public void setCellData(int row, int column, Object data, TreeTableNode node) {
level = getLevel(node);
- setSubRenderer(data);
+ setSubRenderer(row, column, data);
}
protected int getIndentation() {
return level * treeIndent + treeButtonSize.getX();
}
- protected void setSubRenderer(Object colData) {
- subRenderer = getCellRenderer(colData);
+ protected void setSubRenderer(int row, int column, Object colData) {
+ subRenderer = getCellRenderer(colData, column);
if(subRenderer != null) {
- subRenderer.setCellData(level, numColumns, colData);
+ subRenderer.setCellData(row, column, colData);
}
}
@@ -574,12 +598,72 @@ public Widget getCellRenderWidget(int x, int y, int width, int height, boolean i
}
return null;
}
+
+ public Widget updateWidget(Widget existingWidget) {
+ if(subRenderer instanceof CellWidgetCreator) {
+ CellWidgetCreator subCreator = (CellWidgetCreator)subRenderer;
+ return subCreator.updateWidget(existingWidget);
+ }
+ return null;
+ }
+
+ public void positionWidget(Widget widget, int x, int y, int w, int h) {
+ if(subRenderer instanceof CellWidgetCreator) {
+ CellWidgetCreator subCreator = (CellWidgetCreator)subRenderer;
+ int indent = level * treeIndent;
+ subCreator.positionWidget(widget, x+indent, y, Math.max(0, w-indent), h);
+ }
+ }
+ }
+
+ static class WidgetChain extends Widget {
+ final ToggleButton expandButton;
+ Widget userWidget;
+
+ WidgetChain() {
+ setTheme("");
+ expandButton = new ToggleButton();
+ expandButton.setTheme("treeButton");
+ add(expandButton);
+ }
+
+ void setUserWidget(Widget userWidget) {
+ if(this.userWidget != userWidget) {
+ if(this.userWidget != null) {
+ removeChild(1);
+ }
+ this.userWidget = userWidget;
+ if(userWidget != null) {
+ insertChild(userWidget, 1);
+ }
+ }
+ }
}
- class TreeNodeCellRenderer extends TreeLeafCellRenderer implements CellWidgetCreator {
+ class TreeNodeCellRenderer extends TreeLeafCellRenderer {
private NodeState nodeState;
+ @Override
public Widget updateWidget(Widget existingWidget) {
+ if(subRenderer instanceof CellWidgetCreator) {
+ CellWidgetCreator subCreator = (CellWidgetCreator)subRenderer;
+ WidgetChain widgetChain = null;
+ if(existingWidget instanceof WidgetChain) {
+ widgetChain = (WidgetChain)existingWidget;
+ }
+ if(nodeState.hasNoChildren()) {
+ if(widgetChain != null) {
+ existingWidget = null;
+ }
+ return subCreator.updateWidget(existingWidget);
+ }
+ if(widgetChain == null) {
+ widgetChain = new WidgetChain();
+ }
+ widgetChain.expandButton.setModel(nodeState);
+ widgetChain.setUserWidget(subCreator.updateWidget(widgetChain.userWidget));
+ return widgetChain;
+ }
if(nodeState.hasNoChildren()) {
return null;
}
@@ -588,21 +672,35 @@ public Widget updateWidget(Widget existingWidget) {
tb = new ToggleButton();
tb.setTheme("treeButton");
}
- ((ToggleButtonModel)tb.getModel()).setModel(nodeState);
+ tb.setModel(nodeState);
return tb;
}
+ @Override
public void positionWidget(Widget widget, int x, int y, int w, int h) {
int indent = level * treeIndent;
int availWidth = Math.max(0, w-indent);
+ int expandButtonWidth = Math.min(availWidth, treeButtonSize.getX());
widget.setPosition(x + indent, y + (h-treeButtonSize.getY())/2);
- widget.setSize(Math.min(availWidth, treeButtonSize.getX()), treeButtonSize.getY());
+ if(subRenderer instanceof CellWidgetCreator) {
+ CellWidgetCreator subCreator = (CellWidgetCreator)subRenderer;
+ WidgetChain widgetChain = (WidgetChain)widget;
+ ToggleButton expandButton = widgetChain.expandButton;
+ widgetChain.setSize(Math.max(0, w-indent), h);
+ expandButton.setSize(expandButtonWidth, treeButtonSize.getY());
+ if(widgetChain.userWidget != null) {
+ subCreator.positionWidget(widgetChain.userWidget,
+ expandButton.getRight(), y, widget.getWidth(), h);
+ }
+ } else {
+ widget.setSize(expandButtonWidth, treeButtonSize.getY());
+ }
}
public void setCellData(int row, int column, Object data, NodeState nodeState) {
assert nodeState != null;
this.nodeState = nodeState;
- setSubRenderer(data);
+ setSubRenderer(row, column, data);
level = nodeState.level;
}
}
diff --git a/twl/src/de/matthiasmann/twl/ValueAdjuster.java b/twl/src/de/matthiasmann/twl/ValueAdjuster.java
index 14cd7c2..d29d9b2 100644
--- a/twl/src/de/matthiasmann/twl/ValueAdjuster.java
+++ b/twl/src/de/matthiasmann/twl/ValueAdjuster.java
@@ -378,7 +378,7 @@ protected boolean handleEvent(Event evt) {
}
}
}
- return true;
+ return false;
}
} else if(!editField.isVisible() && useMouseWheel && evt.getType() == Event.Type.MOUSE_WHEEL) {
if(evt.getMouseWheelDelta() < 0) {
diff --git a/twl/src/de/matthiasmann/twl/ValueAdjusterFloat.java b/twl/src/de/matthiasmann/twl/ValueAdjusterFloat.java
index a653761..7aa2d20 100644
--- a/twl/src/de/matthiasmann/twl/ValueAdjusterFloat.java
+++ b/twl/src/de/matthiasmann/twl/ValueAdjusterFloat.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2008-2010, Matthias Mann
+ * Copyright (c) 2008-2012, Matthias Mann
*
* All rights reserved.
*
@@ -127,9 +127,6 @@ public FloatModel getModel() {
public void setModel(FloatModel model) {
if(this.model != model) {
removeModelCallback();
- if(this.model != null) {
- this.model.removeCallback(modelCallback);
- }
this.model = model;
if(model != null) {
this.minValue = model.getMinValue();
@@ -231,6 +228,8 @@ protected float parseText(String value) throws ParseException {
protected void syncWithModel() {
cancelEdit();
+ this.minValue = model.getMinValue();
+ this.maxValue = model.getMaxValue();
this.value = model.getValue();
setDisplayText();
}
diff --git a/twl/src/de/matthiasmann/twl/ValueAdjusterInt.java b/twl/src/de/matthiasmann/twl/ValueAdjusterInt.java
index 420efb4..9d70908 100644
--- a/twl/src/de/matthiasmann/twl/ValueAdjusterInt.java
+++ b/twl/src/de/matthiasmann/twl/ValueAdjusterInt.java
@@ -178,6 +178,8 @@ protected String formatText() {
protected void syncWithModel() {
cancelEdit();
+ this.minValue = model.getMinValue();
+ this.maxValue = model.getMaxValue();
this.value = model.getValue();
setDisplayText();
}
diff --git a/twl/src/de/matthiasmann/twl/WheelWidget.java b/twl/src/de/matthiasmann/twl/WheelWidget.java
new file mode 100644
index 0000000..946a711
--- /dev/null
+++ b/twl/src/de/matthiasmann/twl/WheelWidget.java
@@ -0,0 +1,572 @@
+/*
+ * Copyright (c) 2008-2011, Matthias Mann
+ *
+ * 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 Matthias Mann 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 de.matthiasmann.twl;
+
+import de.matthiasmann.twl.model.IntegerModel;
+import de.matthiasmann.twl.model.ListModel;
+import de.matthiasmann.twl.renderer.Image;
+import de.matthiasmann.twl.utils.TypeMapping;
+
+/**
+ * A wheel widget.
+ *
+ * @param The data type for the wheel items
+ *
+ * @author Matthias Mann
+ */
+public class WheelWidget extends Widget {
+
+ public interface ItemRenderer {
+ public Widget getRenderWidget(Object data);
+ }
+
+ private final TypeMapping itemRenderer;
+ private final L listener;
+ private final R renderer;
+ private final Runnable timerCB;
+
+ protected int itemHeight;
+ protected int numVisibleItems;
+ protected Image selectedOverlay;
+
+ private static final int TIMER_INTERVAL = 30;
+ private static final int MIN_SPEED = 3;
+ private static final int MAX_SPEED = 100;
+
+ protected Timer timer;
+ protected int dragStartY;
+ protected long lastDragTime;
+ protected long lastDragDelta;
+ protected int lastDragDist;
+ protected boolean hasDragStart;
+ protected boolean dragActive;
+ protected int scrollOffset;
+ protected int scrollAmount;
+
+ protected ListModel model;
+ protected IntegerModel selectedModel;
+ protected int selected;
+ protected boolean cyclic;
+
+ public WheelWidget() {
+ this.itemRenderer = new TypeMapping();
+ this.listener = new L();
+ this.renderer = new R();
+ this.timerCB = new Runnable() {
+ public void run() {
+ onTimer();
+ }
+ };
+
+ itemRenderer.put(String.class, new StringItemRenderer());
+
+ super.insertChild(renderer, 0);
+ setCanAcceptKeyboardFocus(true);
+ }
+
+ public WheelWidget(ListModel model) {
+ this();
+ this.model = model;
+ }
+
+ public ListModel getModel() {
+ return model;
+ }
+
+ public void setModel(ListModel model) {
+ removeListener();
+ this.model = model;
+ addListener();
+ invalidateLayout();
+ }
+
+ public IntegerModel getSelectedModel() {
+ return selectedModel;
+ }
+
+ public void setSelectedModel(IntegerModel selectedModel) {
+ removeSelectedListener();
+ this.selectedModel = selectedModel;
+ addSelectedListener();
+ }
+
+ public int getSelected() {
+ return selected;
+ }
+
+ public void setSelected(int selected) {
+ int oldSelected = this.selected;
+ if(oldSelected != selected) {
+ this.selected = selected;
+ if(selectedModel != null) {
+ selectedModel.setValue(selected);
+ }
+ firePropertyChange("selected", oldSelected, selected);
+ }
+ }
+
+ public boolean isCyclic() {
+ return cyclic;
+ }
+
+ public void setCyclic(boolean cyclic) {
+ this.cyclic = cyclic;
+ }
+
+ public int getItemHeight() {
+ return itemHeight;
+ }
+
+ public int getNumVisibleItems() {
+ return numVisibleItems;
+ }
+
+ public boolean removeItemRenderer(Class extends T> clazz) {
+ if(itemRenderer.remove(clazz)) {
+ super.removeAllChildren();
+ invalidateLayout();
+ return true;
+ }
+ return false;
+ }
+
+ public void registerItemRenderer(Class extends T> clazz, ItemRenderer value) {
+ itemRenderer.put(clazz, value);
+ invalidateLayout();
+ }
+
+ public void scroll(int amount) {
+ scrollInt(amount);
+ scrollAmount = 0;
+ }
+
+ protected void scrollInt(int amount) {
+ int pos = selected;
+ int half = itemHeight / 2;
+
+ scrollOffset += amount;
+ while(scrollOffset >= half) {
+ scrollOffset -= itemHeight;
+ pos++;
+ }
+ while(scrollOffset <= -half) {
+ scrollOffset += itemHeight;
+ pos--;
+ }
+
+ if(!cyclic) {
+ int n = getNumEntries();
+ if(n > 0) {
+ while(pos >= n) {
+ pos--;
+ scrollOffset += itemHeight;
+ }
+ }
+ while(pos < 0) {
+ pos++;
+ scrollOffset -= itemHeight;
+ }
+ scrollOffset = Math.max(-itemHeight, Math.min(itemHeight, scrollOffset));
+ }
+
+ setSelected(pos);
+
+ if(scrollOffset == 0 && scrollAmount == 0) {
+ stopTimer();
+ } else {
+ startTimer();
+ }
+ }
+
+ public void autoScroll(int dir) {
+ if(dir != 0) {
+ if(scrollAmount != 0 && Integer.signum(scrollAmount) != Integer.signum(dir)) {
+ scrollAmount = dir;
+ } else {
+ scrollAmount += dir;
+ }
+ startTimer();
+ }
+ }
+
+ @Override
+ public int getPreferredInnerHeight() {
+ return numVisibleItems * itemHeight;
+ }
+
+ @Override
+ public int getPreferredInnerWidth() {
+ int width = 0;
+ for(int i=0,n=getNumEntries() ; i 3 && lastDragDelta > 0) {
+ int amount = (int)Math.min(1000, absDist * 100 / lastDragDelta);
+ autoScroll(amount * Integer.signum(lastDragDist));
+ }
+
+ hasDragStart = false;
+ dragActive = false;
+ return true;
+ }
+
+ if(evt.isMouseDragEvent()) {
+ if(hasDragStart) {
+ long time = getTime();
+ dragActive = true;
+ lastDragDist = dragStartY - evt.getMouseY();
+ lastDragDelta = Math.max(1, time - lastDragTime);
+ scroll(lastDragDist);
+ dragStartY = evt.getMouseY();
+ lastDragTime = time;
+ }
+ return true;
+ }
+
+ if(super.handleEvent(evt)) {
+ return true;
+ }
+
+ switch(evt.getType()) {
+ case MOUSE_WHEEL:
+ autoScroll(itemHeight * evt.getMouseWheelDelta());
+ return true;
+
+ case MOUSE_BTNDOWN:
+ if(evt.getMouseButton() == Event.MOUSE_LBUTTON) {
+ dragStartY = evt.getMouseY();
+ lastDragTime = getTime();
+ hasDragStart = true;
+ }
+ return true;
+
+ case KEY_PRESSED:
+ switch(evt.getKeyCode()) {
+ case Event.KEY_UP:
+ autoScroll(-itemHeight);
+ return true;
+ case Event.KEY_DOWN:
+ autoScroll(+itemHeight);
+ return true;
+ }
+ return false;
+ }
+
+ return evt.isMouseEvent();
+ }
+
+ protected long getTime() {
+ GUI gui = getGUI();
+ return (gui != null) ? gui.getCurrentTime() : 0;
+ }
+
+ protected int getNumEntries() {
+ return (model == null) ? 0 : model.getNumEntries();
+ }
+
+ protected Widget getItemRenderer(int i) {
+ T item = model.getEntry(i);
+ if(item != null) {
+ ItemRenderer ir = itemRenderer.get(item.getClass());
+ if(ir != null) {
+ Widget w = ir.getRenderWidget(item);
+ if(w != null) {
+ if(w.getParent() != renderer) {
+ w.setVisible(false);
+ renderer.add(w);
+ }
+ return w;
+ }
+ }
+ }
+ return null;
+ }
+
+ protected void startTimer() {
+ if(timer != null && !timer.isRunning()) {
+ timer.start();
+ }
+ }
+
+ protected void stopTimer() {
+ if(timer != null) {
+ timer.stop();
+ }
+ }
+
+ protected void onTimer() {
+ int amount = scrollAmount;
+ int newAmount = amount;
+
+ if(amount == 0 && !dragActive) {
+ amount = -scrollOffset;
+ }
+
+ if(amount != 0) {
+ int absAmount = Math.abs(amount);
+ int speed = absAmount * TIMER_INTERVAL / 200;
+ int dir = Integer.signum(amount) * Math.min(absAmount,
+ Math.max(MIN_SPEED, Math.min(MAX_SPEED, speed)));
+
+ if(newAmount != 0) {
+ newAmount -= dir;
+ }
+
+ scrollAmount = newAmount;
+ scrollInt(dir);
+ }
+ }
+
+ @Override
+ protected void layout() {
+ layoutChildFullInnerArea(renderer);
+ }
+
+ @Override
+ protected void applyTheme(ThemeInfo themeInfo) {
+ super.applyTheme(themeInfo);
+ applyThemeWheel(themeInfo);
+ }
+
+ protected void applyThemeWheel(ThemeInfo themeInfo) {
+ itemHeight = themeInfo.getParameter("itemHeight", 10);
+ numVisibleItems = themeInfo.getParameter("visibleItems", 5);
+ selectedOverlay = themeInfo.getImage("selectedOverlay");
+ invalidateLayout();
+ }
+
+ @Override
+ protected void afterAddToGUI(GUI gui) {
+ super.afterAddToGUI(gui);
+ addListener();
+ addSelectedListener();
+ timer = gui.createTimer();
+ timer.setCallback(timerCB);
+ timer.setDelay(TIMER_INTERVAL);
+ timer.setContinuous(true);
+ }
+
+ @Override
+ protected void beforeRemoveFromGUI(GUI gui) {
+ timer.stop();
+ timer = null;
+ removeListener();
+ removeSelectedListener();
+ super.beforeRemoveFromGUI(gui);
+ }
+
+ @Override
+ public void insertChild(Widget child, int index) throws UnsupportedOperationException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void removeAllChildren() throws UnsupportedOperationException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public Widget removeChild(int index) throws UnsupportedOperationException {
+ throw new UnsupportedOperationException();
+ }
+
+ private void addListener() {
+ if(model != null) {
+ model.addChangeListener(listener);
+ }
+ }
+
+ private void removeListener() {
+ if(model != null) {
+ model.removeChangeListener(listener);
+ }
+ }
+
+ private void addSelectedListener() {
+ if(selectedModel != null) {
+ selectedModel.addCallback(listener);
+ syncSelected();
+ }
+ }
+
+ private void removeSelectedListener() {
+ if(selectedModel != null) {
+ selectedModel.removeCallback(listener);
+ }
+ }
+
+ void syncSelected() {
+ setSelected(selectedModel.getValue());
+ }
+
+ void entriesDeleted(int first, int last) {
+ if(selected > first) {
+ if(selected > last) {
+ setSelected(selected - (last-first+1));
+ } else {
+ setSelected(first);
+ }
+ }
+ invalidateLayout();
+ }
+
+ void entriesInserted(int first, int last) {
+ if(selected >= first) {
+ setSelected(selected + (last-first+1));
+ }
+ invalidateLayout();
+ }
+
+ class L implements ListModel.ChangeListener, Runnable {
+ public void allChanged() {
+ invalidateLayout();
+ }
+ public void entriesChanged(int first, int last) {
+ invalidateLayout();
+ }
+ public void entriesDeleted(int first, int last) {
+ WheelWidget.this.entriesDeleted(first, last);
+ }
+ public void entriesInserted(int first, int last) {
+ WheelWidget.this.entriesInserted(first, last);
+ }
+ public void run() {
+ syncSelected();
+ }
+ }
+
+ class R extends Widget {
+ public R() {
+ setTheme("");
+ setClip(true);
+ }
+
+ @Override
+ protected void paintWidget(GUI gui) {
+ if(model == null) {
+ return;
+ }
+
+ int width = getInnerWidth();
+ int x = getInnerX();
+ int y = getInnerY();
+
+ int numItems = model.getNumEntries();
+ int numDraw = numVisibleItems;
+ int startIdx = selected - numVisibleItems/2;
+
+ if((numDraw & 1) == 0) {
+ y -= itemHeight / 2;
+ numDraw++;
+ }
+
+ if(scrollOffset > 0) {
+ y -= scrollOffset;
+ numDraw++;
+ }
+ if(scrollOffset < 0) {
+ y -= itemHeight + scrollOffset;
+ numDraw++;
+ startIdx--;
+ }
+
+ main: for(int i=0 ; i= numItems) {
+ if(!cyclic) {
+ continue main;
+ }
+ idx -= numItems;
+ }
+
+ Widget w = getItemRenderer(idx);
+ if(w != null) {
+ w.setSize(width, itemHeight);
+ w.setPosition(x, y + i*itemHeight);
+ w.validateLayout();
+ paintChild(gui, w);
+ }
+ }
+ }
+
+ @Override
+ public void invalidateLayout() {
+ }
+
+ @Override
+ protected void sizeChanged() {
+ }
+ }
+
+ public static class StringItemRenderer extends Label implements WheelWidget.ItemRenderer {
+ public StringItemRenderer() {
+ setCache(false);
+ }
+
+ public Widget getRenderWidget(Object data) {
+ setText(String.valueOf(data));
+ return this;
+ }
+
+ @Override
+ protected void sizeChanged() {
+ }
+ }
+}
diff --git a/twl/src/de/matthiasmann/twl/Widget.java b/twl/src/de/matthiasmann/twl/Widget.java
index e66a4fd..38a6bb1 100644
--- a/twl/src/de/matthiasmann/twl/Widget.java
+++ b/twl/src/de/matthiasmann/twl/Widget.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2008-2010, Matthias Mann
+ * Copyright (c) 2008-2012, Matthias Mann
*
* All rights reserved.
*
@@ -32,6 +32,8 @@
import de.matthiasmann.twl.renderer.AnimationState.StateKey;
import de.matthiasmann.twl.renderer.MouseCursor;
import de.matthiasmann.twl.renderer.Image;
+import de.matthiasmann.twl.renderer.OffscreenRenderer;
+import de.matthiasmann.twl.renderer.OffscreenSurface;
import de.matthiasmann.twl.renderer.Renderer;
import de.matthiasmann.twl.theme.ThemeManager;
import de.matthiasmann.twl.utils.TextUtil;
@@ -50,6 +52,13 @@
/**
* Root of the TWL class hierarchy.
*
+ *
When subclassing the following methods should be overridden to ensure
+ * correct layout behavior:
+ *
{@link #layout() }
+ *
{@link #getPreferredInnerWidth() }
+ *
{@link #getPreferredInnerHeight() }
+ *
+ *
*
The following methods are events and can be overridden when needed:
*
{@link #afterAddToGUI(de.matthiasmann.twl.GUI) }
*
{@link #allChildrenRemoved() }
@@ -68,6 +77,11 @@
*
{@link #widgetDisabled() }
*
*
+ *
NOTE: The only thread safe methods of TWL are:
+ *
{@link #getGUI() }
+ *
{@link GUI#invokeLater(java.lang.Runnable) }
+ *
+ *
* @author Matthias Mann
*/
public class Widget {
@@ -77,8 +91,6 @@ public class Widget {
public static final StateKey STATE_HAS_FOCUSED_CHILD = StateKey.get("hasFocusedChild");
public static final StateKey STATE_DISABLED = StateKey.get("disabled");
- private static final int FOCUS_KEY = Event.KEY_TAB;
-
private static final int LAYOUT_INVALID_LOCAL = 1;
private static final int LAYOUT_INVALID_GLOBAL = 3;
@@ -104,6 +116,8 @@ public class Widget {
private TintAnimator tintAnimator;
private PropertyChangeSupport propertyChangeSupport;
volatile GUI guiInstance;
+ private OffscreenSurface offscreenSurface;
+ private RenderOffscreen renderOffscreen;
private final AnimationState animState;
private final boolean sharedAnimState;
@@ -118,6 +132,11 @@ public class Widget {
private short maxWidth;
private short maxHeight;
+ private short offscreenExtraLeft;
+ private short offscreenExtraTop;
+ private short offscreenExtraRight;
+ private short offscreenExtraBottom;
+
private ArrayList children;
private Widget lastChildMouseOver;
private Widget focusChild;
@@ -136,14 +155,28 @@ public class Widget {
*/
private static final ThreadLocal focusTransferInfo = new ThreadLocal();
+ /**
+ * Creates a Widget with it's own animation state
+ *
+ *
The initial theme name is the lower case version of the simple class
+ * name of the concrete subclass - or in pseudo code:
+ *
{@code getClass().getSimpleName().toLowerCase() }
+ *
+ * @see #setTheme(java.lang.String)
+ */
public Widget() {
this(null, false);
}
/**
* Creates a Widget with a shared animation state
+ *
+ *
The initial theme name is the lower case version of the simple class
+ * name of the concrete subclass - or in pseudo code:
+ *
{@code getClass().getSimpleName().toLowerCase() }
*
* @param animState the animation state to share, can be null
+ * @see #setTheme(java.lang.String)
*/
public Widget(AnimationState animState) {
this(animState, false);
@@ -151,9 +184,15 @@ public Widget(AnimationState animState) {
/**
* Creates a Widget with a shared or inherited animation state
+ *
+ *
The initial theme name is the lower case version of the simple class
+ * name of the concrete subclass - or in pseudo code:
+ *
{@code getClass().getSimpleName().toLowerCase() }
*
* @param animState the animation state to share or inherit, can be null
- * @param inherit true if the animation state should be inherited false for sharing
+ * @param inherit true if the animation state should be inherited, false for sharing
+ * @see AnimationState#AnimationState(de.matthiasmann.twl.AnimationState)
+ * @see #setTheme(java.lang.String)
*/
public Widget(AnimationState animState, boolean inherit) {
// determine the default theme name from the class name of this instance
@@ -671,6 +710,56 @@ public boolean setBorderSize(Border border) {
}
}
+ public short getOffscreenExtraTop() {
+ return offscreenExtraTop;
+ }
+
+ public short getOffscreenExtraLeft() {
+ return offscreenExtraLeft;
+ }
+
+ public short getOffscreenExtraBottom() {
+ return offscreenExtraBottom;
+ }
+
+ public short getOffscreenExtraRight() {
+ return offscreenExtraRight;
+ }
+
+ /**
+ * Sets the offscreen rendering extra area for this widget.
+ * @param top the extra area on top
+ * @param left the extra area on left
+ * @param bottom the extra area on bottom
+ * @param right the extra area on right
+ * @throws IllegalArgumentException if any of the parameters is negative.
+ * @see #setRenderOffscreen(de.matthiasmann.twl.Widget.RenderOffscreen)
+ */
+ public void setOffscreenExtra(int top, int left, int bottom, int right) {
+ if(top < 0 || left < 0 || bottom < 0 || right < 0) {
+ throw new IllegalArgumentException("negative offscreen extra size");
+ }
+ this.offscreenExtraTop = (short)top;
+ this.offscreenExtraLeft = (short)left;
+ this.offscreenExtraBottom = (short)bottom;
+ this.offscreenExtraRight = (short)right;
+ }
+
+ /**
+ * Sets the offscreen rendering extra area for this widget.
+ * @param offscreenExtra the border object or null for no extra area
+ * @throws IllegalArgumentException if any of the values is negative.
+ * @see #setRenderOffscreen(de.matthiasmann.twl.Widget.RenderOffscreen)
+ */
+ public void setOffscreenExtra(Border offscreenExtra) {
+ if(offscreenExtra == null) {
+ setOffscreenExtra(0, 0, 0, 0);
+ } else {
+ setOffscreenExtra(offscreenExtra.getBorderTop(), offscreenExtra.getBorderLeft(),
+ offscreenExtra.getBorderBottom(), offscreenExtra.getBorderRight());
+ }
+ }
+
/**
* Returns the minimum width of the widget.
* Layout manager will allocate atleast the minimum width to a widget even
@@ -1028,6 +1117,10 @@ public boolean isFocusKeyEnabled() {
/**
* Controls the handling of the FOCUS_KEY.
+ *
The default is true.
+ *
When enabled the focus key (TAB) will cycle through all (indirect)
+ * children which can receive keyboard focus. The order is defined
+ * by {@link #getKeyboardFocusOrder() }.
* @param focusKeyEnabled if true this widget will handle the focus key.
*/
public void setFocusKeyEnabled(boolean focusKeyEnabled) {
@@ -1069,6 +1162,19 @@ public Image getOverlay() {
public void setOverlay(Image overlay) {
this.overlay = overlay;
}
+
+ /**
+ * Returns the mouse cursor which should be used for the given
+ * mouse coordinates and modifiers.
+ *
+ * The default implementation calls {@link #getMouseCursor() }
+ *
+ * @param evt only {@link Event#getMouseX() }, {@link Event#getMouseY() } and {@link Event#getModifiers() } are valid.
+ * @return the mouse cursor or null when no mouse cursor is defined for this widget
+ */
+ public MouseCursor getMouseCursor(Event evt) {
+ return getMouseCursor();
+ }
public MouseCursor getMouseCursor() {
return mouseCursor;
@@ -1143,8 +1249,8 @@ public void insertChild(Widget child, int index) throws IndexOutOfBoundsExceptio
if(index < 0 || index > children.size()) {
throw new IndexOutOfBoundsException();
}
+ child.setParent(this); // can throw exception - see PopupWindow
children.add(index, child);
- child.parent = this;
GUI gui = getGUI();
if(gui != null) {
child.recursivelySetGUI(gui);
@@ -1261,6 +1367,10 @@ public void destroy() {
children.get(i).destroy();
}
}
+ if(offscreenSurface != null) {
+ offscreenSurface.destroy();
+ offscreenSurface = null;
+ }
}
public boolean canAcceptKeyboardFocus() {
@@ -1284,7 +1394,7 @@ public void setDepthFocusTraversal(boolean depthFocusTraversal) {
*
*
Use with care - users don't expect focus changes while working with the UI
*
- *
Focus transfer only works when the widget is added to the GUi tree.
+ *
Focus transfer only works when the widget is added to the GUI tree.
* See {@link #getGUI()}.
*
* @return true if keyboard focus was transfered to this widget.
@@ -1381,6 +1491,23 @@ public void setTintAnimator(TintAnimator tintAnimator) {
this.tintAnimator = tintAnimator;
}
+ /**
+ * Returns the currently active offscreen rendering delegate or null if none was set
+ * @return the currently active offscreen rendering delegate or null if none was set
+ */
+ public RenderOffscreen getRenderOffscreen() {
+ return renderOffscreen;
+ }
+
+ /**
+ * Sets set offscreen rendering delegate. Can be null to disable offscreen rendering.
+ * @param renderOffscreen the offscreen rendering delegate.
+ */
+ public void setRenderOffscreen(RenderOffscreen renderOffscreen) {
+ this.renderOffscreen = renderOffscreen;
+ }
+
+
/**
* Returns the currently set tooltip content.
* @return the currently set tooltip content. Can be null.
@@ -1482,6 +1609,7 @@ protected void applyTheme(ThemeInfo themeInfo) {
applyThemeBackground(themeInfo);
applyThemeOverlay(themeInfo);
applyThemeBorder(themeInfo);
+ applyThemeOffscreenExtra(themeInfo);
applyThemeMinSize(themeInfo);
applyThemeMaxSize(themeInfo);
applyThemeMouseCursor(themeInfo);
@@ -1502,6 +1630,10 @@ protected void applyThemeBorder(ThemeInfo themeInfo) {
setBorderSize(themeInfo.getParameterValue("border", false, Border.class));
}
+ protected void applyThemeOffscreenExtra(ThemeInfo themeInfo) {
+ setOffscreenExtra(themeInfo.getParameterValue("offscreenExtra", false, Border.class));
+ }
+
protected void applyThemeMinSize(ThemeInfo themeInfo) {
setMinSize(
themeInfo.getParameter("minWidth", 0),
@@ -1569,7 +1701,20 @@ protected Object getTooltipContentAt(int mouseX, int mouseY) {
protected void updateTooltip() {
GUI gui = getGUI();
if(gui != null) {
- gui.requestToolTipUpdate(this);
+ gui.requestTooltipUpdate(this, false);
+ }
+ }
+
+ /**
+ * If this widget currently has an open tooltip then this tooltip is reset
+ * and the tooltip timer is restarted.
+ *
+ * @see #getTooltipContent()
+ */
+ protected void resetTooltip() {
+ GUI gui = getGUI();
+ if(gui != null) {
+ gui.requestTooltipUpdate(this, true);
}
}
@@ -1896,26 +2041,22 @@ protected void keyboardFocusGained(FocusGainedCause cause, Widget previousWidget
* This method is called when this widget has been disabled,
* either directly or one of it's parents.
*
- * The default implementation does nothing.
+ *
The default implementation does nothing.
*/
protected void widgetDisabled() {
}
/**
* Paints this widget and it's children.
- * A subclass should overwrite paintWidget() instead of this function.
+ *
A subclass should overwrite paintWidget() instead of this function.
*
- * The default implementation calls the following method in order:
- * paintBackground(gui)
- * paintWidget(gui)
- * paintChildren(gui)
- * paintOverlay(gui)
+ *
The default implementation calls the following method in order:
+ *
{@link #paintBackground(de.matthiasmann.twl.GUI)}
+ *
{@link #paintWidget(de.matthiasmann.twl.GUI)}
+ *
{@link #paintChildren(de.matthiasmann.twl.GUI)}
+ *
{@link #paintOverlay(de.matthiasmann.twl.GUI)}
*
* @param gui the GUI object
- * @see #paintBackground(de.matthiasmann.twl.GUI)
- * @see #paintWidget(de.matthiasmann.twl.GUI)
- * @see #paintChildren(de.matthiasmann.twl.GUI)
- * @see #paintOverlay(de.matthiasmann.twl.GUI)
*/
protected void paint(GUI gui) {
paintBackground(gui);
@@ -1925,9 +2066,14 @@ protected void paint(GUI gui) {
}
/**
- * Called by paint() after painting the background and before painting all children.
- * This should be overwritten instead of paint() if normal themeable
- * painting is desired by the subclass.
+ * Called by {@link #paint(de.matthiasmann.twl.GUI)} after painting the
+ * background and before painting all children.
+ *
+ *
This should be overwritten instead of {@code paint} if normal themeable
+ * painting is desired by the subclass.
+ *
+ *
The default implementation does nothing.
+ *
* @param gui the GUI object - it's the same as getGUI()
*/
protected void paintWidget(GUI gui) {
@@ -1936,6 +2082,7 @@ protected void paintWidget(GUI gui) {
/**
* Paint the background image of this widget.
* @param gui the GUI object
+ * @see #paint(de.matthiasmann.twl.GUI)
*/
protected void paintBackground(GUI gui) {
Image bgImage = getBackground();
@@ -1947,6 +2094,7 @@ protected void paintBackground(GUI gui) {
/**
* Paints the overlay image of this widget.
* @param gui the GUI object
+ * @see #paint(de.matthiasmann.twl.GUI)
*/
protected void paintOverlay(GUI gui) {
Image ovImage = getOverlay();
@@ -1958,6 +2106,7 @@ protected void paintOverlay(GUI gui) {
/**
* Paints all children in index order. Invisible children are skipped.
* @param gui the GUI object
+ * @see #paint(de.matthiasmann.twl.GUI)
*/
protected void paintChildren(GUI gui) {
if(children != null) {
@@ -1983,6 +2132,18 @@ protected void paintChild(GUI gui, Widget child) {
child.drawWidget(gui);
}
+ /**
+ * Called after all other widgets have been rendered when a drag operation is in progress.
+ * The mouse position can be outsife of this widget
+ *
+ * @param gui the GUI object
+ * @param mouseX the current mouse X position
+ * @param mouseY the current mouse Y position
+ * @param modifier the current active modifiers - see {@link Event#getModifiers() }
+ */
+ protected void paintDragOverlay(GUI gui, int mouseX, int mouseY, int modifier) {
+ }
+
/**
* Invalidates only the layout of this widget. Does not invalidate the layout of the parent.
* Should only be used for things like scrolling.
@@ -2027,6 +2188,13 @@ protected void layoutChildrenFullInnerArea() {
}
}
+ /**
+ * Returns all children of this widget in their focus travel order.
+ *
The returned list is only iterated and not stored.
+ *
The default implementation just returns an unmodifable view of
+ * the internal children list.
The returned object can be reused on the next call and should not
+ * be stored by the caller.
+ *
+ * @param widget the widget
+ * @return the extra area in {@code top, left, right, bottom} order or null
+ */
+ public int[] getEffectExtraArea(Widget widget);
+
+ /**
+ * Called before offscreen rendering is started.
+ *
+ *
NOTE: when this function returns false none of the paint methods
+ * of that widget are called which might effect some widgets.
+ *
+ *
If you are unsure it is always safer to return true.
+ *
+ * @param gui the GUI instance
+ * @param widget the widget
+ * @param surface the previous offscreen surface - never null
+ * @return true if the surface needs to be updated, false if no new rendering should be done
+ */
+ public boolean needPainting(GUI gui, Widget widget, OffscreenSurface surface);
+ }
+
+ public interface OffscreenMouseAdjustments extends RenderOffscreen {
+
+ /**
+ * Called when mouse events are routed for the widget.
+ *
+ *
All mouse coordinates in TWL are absolute.
+ *
+ *
The returned object can be reused on the next call and should not
+ * be stored by the caller.
+ *
+ * @param widget the widget
+ * @param evt the mouse event
+ * @return the new mouse coordinates in {@code x, y} order
+ */
+ public int[] adjustMouseCoordinates(Widget widget, Event evt);
+ }
}
diff --git a/twl/src/de/matthiasmann/twl/license.html b/twl/src/de/matthiasmann/twl/license.html
index 7518c06..3bee132 100644
--- a/twl/src/de/matthiasmann/twl/license.html
+++ b/twl/src/de/matthiasmann/twl/license.html
@@ -1,6 +1,6 @@
-
Copyright (c) 2008-2011, Matthias Mann
+
Copyright (c) 2008-2012, Matthias Mann
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
diff --git a/twl/src/de/matthiasmann/twl/license.txt b/twl/src/de/matthiasmann/twl/license.txt
index 28cd5b8..df40044 100644
--- a/twl/src/de/matthiasmann/twl/license.txt
+++ b/twl/src/de/matthiasmann/twl/license.txt
@@ -1,4 +1,4 @@
-Copyright (c) 2008-2011, Matthias Mann
+Copyright (c) 2008-2012, Matthias Mann
All rights reserved.
diff --git a/twl/src/de/matthiasmann/twl/model/AbstractProperty.java b/twl/src/de/matthiasmann/twl/model/AbstractProperty.java
index 99f52ef..12f4857 100644
--- a/twl/src/de/matthiasmann/twl/model/AbstractProperty.java
+++ b/twl/src/de/matthiasmann/twl/model/AbstractProperty.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2008-2009, Matthias Mann
+ * Copyright (c) 2008-2012, Matthias Mann
*
* All rights reserved.
*
@@ -39,7 +39,7 @@
*/
public abstract class AbstractProperty implements Property {
- private Runnable[] valueChangedCallbacks = null;
+ private Runnable[] valueChangedCallbacks;
public void addValueChangedCallback(Runnable cb) {
valueChangedCallbacks = CallbackSupport.addCallbackToList(
@@ -51,6 +51,10 @@ public void removeValueChangedCallback(Runnable cb) {
valueChangedCallbacks, cb);
}
+ public boolean hasValueChangedCallbacks() {
+ return valueChangedCallbacks != null;
+ }
+
protected void fireValueChangedCallback() {
CallbackSupport.fireCallbacks(valueChangedCallbacks);
}
diff --git a/twl/src/de/matthiasmann/twl/model/AbstractTableModel.java b/twl/src/de/matthiasmann/twl/model/AbstractTableModel.java
index d4186b1..9c3eb4e 100644
--- a/twl/src/de/matthiasmann/twl/model/AbstractTableModel.java
+++ b/twl/src/de/matthiasmann/twl/model/AbstractTableModel.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2008-2010, Matthias Mann
+ * Copyright (c) 2008-2012, Matthias Mann
*
* All rights reserved.
*
@@ -51,6 +51,10 @@ public void removeChangeListener(ChangeListener listener) {
callbacks = CallbackSupport.removeCallbackFromList(callbacks, listener);
}
+ protected boolean hasCallbacks() {
+ return callbacks != null;
+ }
+
protected void fireRowsInserted(int idx, int count) {
if(callbacks != null) {
for(ChangeListener cl : callbacks) {
diff --git a/twl/src/de/matthiasmann/twl/model/BooleanModel.java b/twl/src/de/matthiasmann/twl/model/BooleanModel.java
index 36eab98..7a0f5ea 100644
--- a/twl/src/de/matthiasmann/twl/model/BooleanModel.java
+++ b/twl/src/de/matthiasmann/twl/model/BooleanModel.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2008, Matthias Mann
+ * Copyright (c) 2008-2011, Matthias Mann
*
* All rights reserved.
*
@@ -29,19 +29,17 @@
*/
package de.matthiasmann.twl.model;
+import de.matthiasmann.twl.utils.WithRunnableCallback;
+
/**
* A generic boolean model.
*
* @author Matthias Mann
*/
-public interface BooleanModel {
+public interface BooleanModel extends WithRunnableCallback {
public boolean getValue();
public void setValue(boolean value);
-
- public void addCallback(Runnable callback);
-
- public void removeCallback(Runnable callback);
}
diff --git a/twl/src/de/matthiasmann/twl/model/ColorModel.java b/twl/src/de/matthiasmann/twl/model/ColorModel.java
new file mode 100644
index 0000000..20d9154
--- /dev/null
+++ b/twl/src/de/matthiasmann/twl/model/ColorModel.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (c) 2008-2011, Matthias Mann
+ *
+ * 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 Matthias Mann 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 de.matthiasmann.twl.model;
+
+import de.matthiasmann.twl.Color;
+import de.matthiasmann.twl.utils.WithRunnableCallback;
+
+/**
+ * A model which stores a color value.
+ *
+ * @author Matthias Mann
+ */
+public interface ColorModel extends WithRunnableCallback {
+
+ public Color getValue();
+
+ public void setValue(Color value);
+
+}
diff --git a/twl/src/de/matthiasmann/twl/model/DateModel.java b/twl/src/de/matthiasmann/twl/model/DateModel.java
new file mode 100644
index 0000000..8fd1d31
--- /dev/null
+++ b/twl/src/de/matthiasmann/twl/model/DateModel.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (c) 2008-2011, Matthias Mann
+ *
+ * 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 Matthias Mann 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 de.matthiasmann.twl.model;
+
+import de.matthiasmann.twl.utils.WithRunnableCallback;
+
+/**
+ * A date model which stores the current date in milliseconds
+ *
+ * @author Matthias Mann
+ * @see System#currentTimeMillis()
+ */
+public interface DateModel extends WithRunnableCallback {
+
+ public long getValue();
+
+ public void setValue(long date);
+
+}
diff --git a/twl/src/de/matthiasmann/twl/model/DefaultEditFieldModel.java b/twl/src/de/matthiasmann/twl/model/DefaultEditFieldModel.java
new file mode 100644
index 0000000..26ab910
--- /dev/null
+++ b/twl/src/de/matthiasmann/twl/model/DefaultEditFieldModel.java
@@ -0,0 +1,117 @@
+/*
+ * Copyright (c) 2008-2011, Matthias Mann
+ *
+ * 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 Matthias Mann 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 de.matthiasmann.twl.model;
+
+import de.matthiasmann.twl.utils.CallbackSupport;
+
+/**
+ * An {@code EditFieldModel} based on a {@link StringBuilder}
+ *
+ * @author Matthias Mann
+ */
+public class DefaultEditFieldModel implements EditFieldModel {
+
+ private final StringBuilder sb;
+ private Callback[] callbacks;
+
+ public DefaultEditFieldModel() {
+ this.sb = new StringBuilder();
+ }
+
+ public int length() {
+ return sb.length();
+ }
+
+ public char charAt(int index) {
+ return sb.charAt(index);
+ }
+
+ public CharSequence subSequence(int start, int end) {
+ return sb.subSequence(start, end);
+ }
+
+ @Override
+ public String toString() {
+ return sb.toString();
+ }
+
+ public void addCallback(Callback callback) {
+ callbacks = CallbackSupport.addCallbackToList(callbacks, callback, Callback.class);
+ }
+
+ public void removeCallback(Callback callback) {
+ callbacks = CallbackSupport.removeCallbackFromList(callbacks, callback);
+ }
+
+ public int replace(int start, int count, String replacement) {
+ checkRange(start, count);
+ int replacementLength = replacement.length();
+ if(count > 0 || replacementLength > 0) {
+ sb.replace(start, start+count, replacement);
+ fireCallback(start, count, replacementLength);
+ }
+ return replacementLength;
+ }
+
+ public boolean replace(int start, int count, char replacement) {
+ checkRange(start, count);
+ if(count == 0) {
+ sb.insert(start, replacement);
+ } else {
+ sb.delete(start, start+count-1);
+ sb.setCharAt(start, replacement);
+ }
+ fireCallback(start, count, 1);
+ return true;
+ }
+
+ public String substring(int start, int end) {
+ return sb.substring(start, end);
+ }
+
+ private void checkRange(int start, int count) {
+ int len = sb.length();
+ if(start < 0 || start > len) {
+ throw new StringIndexOutOfBoundsException(start);
+ }
+ if(count < 0 || count > len - start) {
+ throw new StringIndexOutOfBoundsException();
+ }
+ }
+
+ private void fireCallback(int start, int oldCount, int newCount) {
+ Callback[] cbs = this.callbacks;
+ if(cbs != null) {
+ for(Callback cb : cbs) {
+ cb.charactersChanged(start, oldCount, newCount);
+ }
+ }
+ }
+}
diff --git a/twl/src/de/matthiasmann/twl/model/EditFieldModel.java b/twl/src/de/matthiasmann/twl/model/EditFieldModel.java
new file mode 100644
index 0000000..413522c
--- /dev/null
+++ b/twl/src/de/matthiasmann/twl/model/EditFieldModel.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright (c) 2008-2011, Matthias Mann
+ *
+ * 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 Matthias Mann 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 de.matthiasmann.twl.model;
+
+/**
+ *
+ * @author Matthias Mann
+ */
+public interface EditFieldModel extends ObservableCharSequence {
+
+ /**
+ * Replace {@code count} characters starting at {@code start} with the
+ * specified {@code replacement} text.
+ * @param start the start index
+ * @param count the number of characters to replace, can be 0.
+ * @param replacement the replacement text, can be empty.
+ * @return the number of characters which have been inserted, or -1 if
+ * no replacement has been performed.
+ * @throws StringIndexOutOfBoundsException if {@code start} or {@code count}
+ * are outside the sequence
+ * @throws NullPointerException when replacement is {@code null}
+ */
+ public int replace(int start, int count, String replacement);
+
+ /**
+ * Replace {@code count} characters starting at {@code start} with the
+ * specified {@code replacement} character.
+ * @param start the start index
+ * @param count the number of characters to replace, can be 0.
+ * @param replacement the replacement character
+ * @return true if the sequence was changed, false otherwise.
+ * @throws StringIndexOutOfBoundsException if {@code start} or {@code count}
+ * are outside the sequence
+ */
+ public boolean replace(int start, int count, char replacement);
+
+ /**
+ * Returns a String containing the specified range from this sequence.
+ * @param start the start index
+ * @param end the end index
+ * @return the String object
+ * @throws StringIndexOutOfBoundsException if {@code start} or {@code count}
+ * are outside the sequence
+ * @see #subSequence(int, int)
+ */
+ public String substring(int start, int end);
+}
diff --git a/twl/src/de/matthiasmann/twl/model/EnumModel.java b/twl/src/de/matthiasmann/twl/model/EnumModel.java
index 4373cde..72b159c 100644
--- a/twl/src/de/matthiasmann/twl/model/EnumModel.java
+++ b/twl/src/de/matthiasmann/twl/model/EnumModel.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2008, Matthias Mann
+ * Copyright (c) 2008-2011, Matthias Mann
*
* All rights reserved.
*
@@ -29,21 +29,20 @@
*/
package de.matthiasmann.twl.model;
+import de.matthiasmann.twl.utils.WithRunnableCallback;
+
/**
* A generic model for enum values based on a generic enum type.
*
* @param The enum type
* @author Matthias Mann
*/
-public interface EnumModel> {
+public interface EnumModel> extends WithRunnableCallback {
public Class getEnumClass();
public T getValue();
public void setValue(T value);
-
- public void addCallback(Runnable callback);
- public void removeCallback(Runnable callback);
}
diff --git a/twl/src/de/matthiasmann/twl/model/FloatModel.java b/twl/src/de/matthiasmann/twl/model/FloatModel.java
index 1479a09..47dd1ff 100644
--- a/twl/src/de/matthiasmann/twl/model/FloatModel.java
+++ b/twl/src/de/matthiasmann/twl/model/FloatModel.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2008-2010, Matthias Mann
+ * Copyright (c) 2008-2011, Matthias Mann
*
* All rights reserved.
*
@@ -29,12 +29,14 @@
*/
package de.matthiasmann.twl.model;
+import de.matthiasmann.twl.utils.WithRunnableCallback;
+
/**
* A generic model for float values.
*
* @author Matthias Mann
*/
-public interface FloatModel {
+public interface FloatModel extends WithRunnableCallback {
public float getValue();
@@ -43,8 +45,5 @@ public interface FloatModel {
public float getMaxValue();
public void setValue(float value);
-
- public void addCallback(Runnable callback);
-
- public void removeCallback(Runnable callback);
+
}
diff --git a/twl/src/de/matthiasmann/twl/model/HasCallback.java b/twl/src/de/matthiasmann/twl/model/HasCallback.java
index 6d729e0..de484f9 100644
--- a/twl/src/de/matthiasmann/twl/model/HasCallback.java
+++ b/twl/src/de/matthiasmann/twl/model/HasCallback.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2008, Matthias Mann
+ * Copyright (c) 2008-2012, Matthias Mann
*
* All rights reserved.
*
@@ -30,13 +30,14 @@
package de.matthiasmann.twl.model;
import de.matthiasmann.twl.utils.CallbackSupport;
+import de.matthiasmann.twl.utils.WithRunnableCallback;
/**
* A class to manage callbacks.
*
* @author Matthias Mann
*/
-public class HasCallback {
+public class HasCallback implements WithRunnableCallback {
private Runnable[] callbacks;
@@ -59,6 +60,14 @@ public void removeCallback(Runnable callback) {
callbacks = CallbackSupport.removeCallbackFromList(callbacks, callback);
}
+ /**
+ * Returns true when the callback list is not empty
+ * @return true when the callback list is not empty
+ */
+ public boolean hasCallbacks() {
+ return callbacks != null;
+ }
+
/**
* Calls all registered callbacks.
*
diff --git a/twl/src/de/matthiasmann/twl/model/IntegerModel.java b/twl/src/de/matthiasmann/twl/model/IntegerModel.java
index a963e83..7d29180 100644
--- a/twl/src/de/matthiasmann/twl/model/IntegerModel.java
+++ b/twl/src/de/matthiasmann/twl/model/IntegerModel.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2008, Matthias Mann
+ * Copyright (c) 2008-2011, Matthias Mann
*
* All rights reserved.
*
@@ -29,12 +29,14 @@
*/
package de.matthiasmann.twl.model;
+import de.matthiasmann.twl.utils.WithRunnableCallback;
+
/**
* A generic model for integer values.
*
* @author Matthias Mann
*/
-public interface IntegerModel {
+public interface IntegerModel extends WithRunnableCallback {
public int getValue();
@@ -43,8 +45,5 @@ public interface IntegerModel {
public int getMaxValue();
public void setValue(int value);
-
- public void addCallback(Runnable callback);
- public void removeCallback(Runnable callback);
}
diff --git a/twl/src/de/matthiasmann/twl/model/JavaFileSystemModel.java b/twl/src/de/matthiasmann/twl/model/JavaFileSystemModel.java
index 7f0d49a..25bcdf2 100644
--- a/twl/src/de/matthiasmann/twl/model/JavaFileSystemModel.java
+++ b/twl/src/de/matthiasmann/twl/model/JavaFileSystemModel.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2008, Matthias Mann
+ * Copyright (c) 2008-2012, Matthias Mann
*
* All rights reserved.
*
@@ -189,7 +189,11 @@ public boolean accept(File pathname) {
public Object getSpecialFolder(String key) {
File file = null;
if(SPECIAL_FOLDER_HOME.equals(key)) {
- file = new File(System.getProperty("user.home"));
+ try {
+ file = new File(System.getProperty("user.home"));
+ } catch(SecurityException ex) {
+ // ignore
+ }
}
if(file != null && file.canRead() && file.isDirectory()) {
return file;
diff --git a/twl/src/de/matthiasmann/twl/model/ListSelectionModel.java b/twl/src/de/matthiasmann/twl/model/ListSelectionModel.java
new file mode 100644
index 0000000..a806a91
--- /dev/null
+++ b/twl/src/de/matthiasmann/twl/model/ListSelectionModel.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright (c) 2008-2011, Matthias Mann
+ *
+ * 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 Matthias Mann 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 de.matthiasmann.twl.model;
+
+/**
+ * Represents the selection of a list box or combo box.
+ *
+ * Implementation should not register callbacks on the list model.
+ *
+ * @param the data type of the list model
+ * @author Matthias Mann
+ */
+public interface ListSelectionModel extends IntegerModel {
+
+ /**
+ * The index when nothing is selected
+ */
+ public static final int NO_SELECTION = -1;
+
+ public ListModel getListModel();
+
+ /**
+ * Returns the selected entry in the list or null if nothing is selected.
+ * @return the selected entry or null.
+ */
+ public T getSelectedEntry();
+
+ /**
+ * Selects the specified entry or nothing if the entry was not found.
+ *
+ * This method behaves like {@code setSelectedEntry(entry, NO_SELECTION)}
+ *
+ * @param entry the entry to select - can be null.
+ * @return true if the entry was found
+ * @see #setSelectedEntry(java.lang.Object, int)
+ * @see #NO_SELECTION
+ */
+ public boolean setSelectedEntry(T entry);
+
+ /**
+ * Selects the specified entry or the default index if the entry was not found
+ * @param entry the entry to select - can be null.
+ * @param defaultIndex the index to select when the entry was not found
+ * @return true if the entry was found
+ * @see #NO_SELECTION
+ */
+ public boolean setSelectedEntry(T entry, int defaultIndex);
+}
diff --git a/twl/src/de/matthiasmann/twl/model/ObservableCharSequence.java b/twl/src/de/matthiasmann/twl/model/ObservableCharSequence.java
new file mode 100644
index 0000000..9f0c094
--- /dev/null
+++ b/twl/src/de/matthiasmann/twl/model/ObservableCharSequence.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (c) 2008-2011, Matthias Mann
+ *
+ * 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 Matthias Mann 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 de.matthiasmann.twl.model;
+
+/**
+ * A {@link CharSequence} with change callback support.
+ *
+ * @author Matthias Mann
+ */
+public interface ObservableCharSequence extends CharSequence {
+
+ public interface Callback {
+ /**
+ * The sequence has been modified.
+ * @param start the start offset of the change
+ * @param oldCount the number of characters which have been replaced
+ * @param newCount the number of new characters inserted
+ */
+ public void charactersChanged(int start, int oldCount, int newCount);
+ }
+
+ public void addCallback(Callback callback);
+
+ public void removeCallback(Callback callback);
+}
diff --git a/twl/src/de/matthiasmann/twl/model/PersistentColorModel.java b/twl/src/de/matthiasmann/twl/model/PersistentColorModel.java
new file mode 100644
index 0000000..ef91d84
--- /dev/null
+++ b/twl/src/de/matthiasmann/twl/model/PersistentColorModel.java
@@ -0,0 +1,101 @@
+/*
+ * Copyright (c) 2008-2011, Matthias Mann
+ *
+ * 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 Matthias Mann 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 de.matthiasmann.twl.model;
+
+import de.matthiasmann.twl.Color;
+import java.util.prefs.Preferences;
+
+/**
+ * A Persistent color model.
+ *
+ * @author Matthias Mann
+ */
+public class PersistentColorModel extends HasCallback implements ColorModel {
+
+ private final Preferences prefs;
+ private final String prefKey;
+
+ private Color value;
+ private IllegalArgumentException initialError;
+
+ public PersistentColorModel(Preferences prefs, String prefKey, Color defaultValue) {
+ if(prefs == null) {
+ throw new NullPointerException("prefs");
+ }
+ if(prefKey == null) {
+ throw new NullPointerException("prefKey");
+ }
+ if(defaultValue == null) {
+ throw new NullPointerException("defaultValue");
+ }
+
+ this.prefs = prefs;
+ this.prefKey = prefKey;
+ this.value = defaultValue;
+
+ try {
+ String text = prefs.get(prefKey, null);
+ if(text != null) {
+ Color aValue = Color.parserColor(text);
+ if(aValue != null) {
+ value = aValue;
+ } else {
+ initialError = new IllegalArgumentException("Unknown color name: " + text);
+ }
+ }
+ } catch (IllegalArgumentException ex) {
+ initialError = ex;
+ }
+ }
+
+ public IllegalArgumentException getInitialError() {
+ return initialError;
+ }
+
+ public void clearInitialError() {
+ initialError = null;
+ }
+
+ public Color getValue() {
+ return value;
+ }
+
+ public void setValue(Color value) {
+ if(this.value != value) {
+ this.value = value;
+ storeSettings();
+ doCallback();
+ }
+ }
+
+ private void storeSettings() {
+ prefs.put(prefKey, value.toString());
+ }
+}
diff --git a/twl/src/de/matthiasmann/twl/model/SimpleDateModel.java b/twl/src/de/matthiasmann/twl/model/SimpleDateModel.java
new file mode 100644
index 0000000..d5ac23d
--- /dev/null
+++ b/twl/src/de/matthiasmann/twl/model/SimpleDateModel.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright (c) 2008-2011, Matthias Mann
+ *
+ * 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 Matthias Mann 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 de.matthiasmann.twl.model;
+
+/**
+ * A simple date model
+ *
+ * @author Matthias Mann
+ */
+public class SimpleDateModel extends HasCallback implements DateModel {
+
+ private long date;
+
+ /**
+ * Initializes the date to the current system date
+ * @see System#currentTimeMillis()
+ */
+ public SimpleDateModel() {
+ this.date = System.currentTimeMillis();
+ }
+
+ public SimpleDateModel(long date) {
+ this.date = date;
+ }
+
+ public long getValue() {
+ return date;
+ }
+
+ public void setValue(long date) {
+ if(this.date != date) {
+ this.date = date;
+ doCallback();
+ }
+ }
+}
diff --git a/twl/src/de/matthiasmann/twl/model/SimpleListSelectionModel.java b/twl/src/de/matthiasmann/twl/model/SimpleListSelectionModel.java
new file mode 100644
index 0000000..ecd0680
--- /dev/null
+++ b/twl/src/de/matthiasmann/twl/model/SimpleListSelectionModel.java
@@ -0,0 +1,100 @@
+/*
+ * Copyright (c) 2008-2011, Matthias Mann
+ *
+ * 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 Matthias Mann 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 de.matthiasmann.twl.model;
+
+/**
+ * A simple list selection model
+ *
+ * @param the data type of the list model
+ * @author Matthias Mann
+ */
+public class SimpleListSelectionModel extends HasCallback implements ListSelectionModel {
+
+ private final ListModel listModel;
+ private int selected;
+
+ public SimpleListSelectionModel(ListModel listModel) {
+ if(listModel == null) {
+ throw new NullPointerException("listModel");
+ }
+ this.listModel = listModel;
+ }
+
+ public ListModel getListModel() {
+ return listModel;
+ }
+
+ public T getSelectedEntry() {
+ if(selected >= 0 && selected < listModel.getNumEntries()) {
+ return listModel.getEntry(selected);
+ } else {
+ return null;
+ }
+ }
+
+ public boolean setSelectedEntry(T entry) {
+ return setSelectedEntry(entry, NO_SELECTION);
+ }
+
+ public boolean setSelectedEntry(T entry, int defaultIndex) {
+ if(entry != null) {
+ for(int i=0,n=listModel.getNumEntries() ; i rows) {
- insertRows(rows.size(), rows);
+ insertRows(this.rows.size(), rows);
}
public void insertRow(int index, Object ... data) {
diff --git a/twl/src/de/matthiasmann/twl/model/StringAttributes.java b/twl/src/de/matthiasmann/twl/model/StringAttributes.java
new file mode 100644
index 0000000..496a44e
--- /dev/null
+++ b/twl/src/de/matthiasmann/twl/model/StringAttributes.java
@@ -0,0 +1,323 @@
+/*
+ * Copyright (c) 2008-2011, Matthias Mann
+ *
+ * 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 Matthias Mann 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 de.matthiasmann.twl.model;
+
+import de.matthiasmann.twl.renderer.AnimationState;
+import de.matthiasmann.twl.renderer.AnimationState.StateKey;
+import de.matthiasmann.twl.renderer.AttributedString;
+import java.util.ArrayList;
+import java.util.BitSet;
+
+/**
+ *
+ * @author Matthias Mann
+ */
+public class StringAttributes implements AttributedString {
+
+ private final CharSequence seq;
+ private final AnimationState baseAnimState;
+ private final ArrayList markers;
+
+ private int position;
+ private int markerIdx;
+
+ private StringAttributes(AnimationState baseAnimState, CharSequence seq) {
+ if(seq == null) {
+ throw new NullPointerException("seq");
+ }
+
+ this.seq = seq;
+ this.baseAnimState = baseAnimState;
+ this.markers = new ArrayList();
+ }
+
+ public StringAttributes(String text, AnimationState baseAnimState) {
+ this(baseAnimState, text);
+ }
+
+ public StringAttributes(String text) {
+ this(text, null);
+ }
+
+ public StringAttributes(ObservableCharSequence cs, AnimationState baseAnimState) {
+ this(baseAnimState, cs);
+
+ cs.addCallback(new ObservableCharSequence.Callback() {
+ public void charactersChanged(int start, int oldCount, int newCount) {
+ if(start < 0) {
+ throw new IllegalArgumentException("start");
+ }
+ if(oldCount > 0) {
+ delete(start, oldCount);
+ }
+ if(newCount > 0) {
+ insert(start, newCount);
+ }
+ }
+ });
+ }
+
+ public StringAttributes(ObservableCharSequence cs) {
+ this(cs, null);
+ }
+
+ public char charAt(int index) {
+ return seq.charAt(index);
+ }
+
+ public int length() {
+ return seq.length();
+ }
+
+ public CharSequence subSequence(int start, int end) {
+ return seq.subSequence(start, end);
+ }
+
+ @Override
+ public String toString() {
+ return seq.toString();
+ }
+
+ public int getPosition() {
+ return position;
+ }
+
+ public void setPosition(int pos) {
+ if(pos < 0 || pos > seq.length()) {
+ throw new IllegalArgumentException("pos");
+ }
+ this.position = pos;
+
+ int idx = find(pos);
+ if(idx >= 0) {
+ this.markerIdx = idx;
+ } else if(pos > lastMarkerPos()) {
+ this.markerIdx = markers.size();
+ } else {
+ // select the marker to the left
+ this.markerIdx = (idx & IDX_MASK) - 1;
+ }
+ }
+
+ public int advance() {
+ if(markerIdx+1 < markers.size()) {
+ markerIdx++;
+ position = markers.get(markerIdx).position;
+ } else {
+ position = seq.length();
+ }
+ return position;
+ }
+
+ public boolean getAnimationState(StateKey state) {
+ if(markerIdx >= 0 && markerIdx < markers.size()) {
+ Marker marker = markers.get(markerIdx);
+ int bitIdx = state.getID() << 1;
+ if(marker.get(bitIdx)) {
+ return marker.get(bitIdx+1);
+ }
+ }
+ if(baseAnimState != null) {
+ return baseAnimState.getAnimationState(state);
+ }
+ return false;
+ }
+
+ public int getAnimationTime(StateKey state) {
+ if(baseAnimState != null) {
+ return baseAnimState.getAnimationTime(state);
+ }
+ return 0;
+ }
+
+ public boolean getShouldAnimateState(StateKey state) {
+ if(baseAnimState != null) {
+ return baseAnimState.getShouldAnimateState(state);
+ }
+ return false;
+ }
+
+ public void setAnimationState(StateKey key, int from, int end, boolean active) {
+ if(key == null) {
+ throw new NullPointerException("key");
+ }
+ if(from > end) {
+ throw new IllegalArgumentException("negative range");
+ }
+ if(from < 0 || end > seq.length()) {
+ throw new IllegalArgumentException("range outside of sequence");
+ }
+ if(from == end) {
+ return;
+ }
+ int fromIdx = markerIndexAt(from);
+ int endIdx = markerIndexAt(end);
+ int bitIdx = key.getID() << 1;
+ for(int i=fromIdx ; i end) {
+ throw new IllegalArgumentException("negative range");
+ }
+ if(from < 0 || end > seq.length()) {
+ throw new IllegalArgumentException("range outside of sequence");
+ }
+ if(from == end) {
+ return;
+ }
+ int fromIdx = markerIndexAt(from);
+ int endIdx = markerIndexAt(end);
+ removeRange(fromIdx, endIdx, key);
+ }
+
+ public void removeAnimationState(StateKey key) {
+ if(key == null) {
+ throw new NullPointerException("key");
+ }
+ removeRange(0, markers.size(), key);
+ }
+
+ private void removeRange(int start, int end, StateKey key) {
+ int bitIdx = key.getID() << 1;
+ for(int i=start ; i 1) {
+ Marker prev = markers.get(0);
+ for(int i=1 ; istartIdx ;) {
+ markers.remove(--idx);
+ }
+ }
+
+ private int lastMarkerPos() {
+ int numMarkers = markers.size();
+ if(numMarkers > 0) {
+ return markers.get(numMarkers-1).position;
+ } else {
+ return 0;
+ }
+ }
+
+ private int markerIndexAt(int pos) {
+ int idx = find(pos);
+ if(idx < 0) {
+ idx &= IDX_MASK;
+ insertMarker(idx, pos);
+ }
+ return idx;
+ }
+
+ private void insertMarker(int idx, int pos) {
+ Marker newMarker = new Marker();
+ if(idx > 0) {
+ Marker leftMarker = markers.get(idx - 1);
+ assert leftMarker.position < pos;
+ newMarker.or(leftMarker);
+ }
+ newMarker.position = pos;
+ markers.add(idx, newMarker);
+ }
+
+ private static final int NOT_FOUND = Integer.MIN_VALUE;
+ private static final int IDX_MASK = Integer.MAX_VALUE;
+
+ private int find(int pos) {
+ int lo = 0;
+ int hi = markers.size();
+ while(lo < hi) {
+ int mid = (lo + hi) >>> 1;
+ int markerPos = markers.get(mid).position;
+ if(pos < markerPos) {
+ hi = mid;
+ } else if(pos > markerPos) {
+ lo = mid + 1;
+ } else {
+ return mid;
+ }
+ }
+ return lo | NOT_FOUND;
+ }
+
+ static class Marker extends BitSet {
+ int position;
+ }
+}
diff --git a/twl/src/de/matthiasmann/twl/model/StringModel.java b/twl/src/de/matthiasmann/twl/model/StringModel.java
index 9c33c04..10fb915 100644
--- a/twl/src/de/matthiasmann/twl/model/StringModel.java
+++ b/twl/src/de/matthiasmann/twl/model/StringModel.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2008-2009, Matthias Mann
+ * Copyright (c) 2008-2011, Matthias Mann
*
* All rights reserved.
*
@@ -29,17 +29,17 @@
*/
package de.matthiasmann.twl.model;
+import de.matthiasmann.twl.utils.WithRunnableCallback;
+
/**
* A string data model.
*
* @author Matthias Mann
*/
-public interface StringModel {
+public interface StringModel extends WithRunnableCallback {
+
String getValue();
void setValue(String value);
-
- public void addCallback(Runnable callback);
-
- public void removeCallback(Runnable callback);
+
}
diff --git a/twl/src/de/matthiasmann/twl/renderer/AnimationState.java b/twl/src/de/matthiasmann/twl/renderer/AnimationState.java
index f3fdb6b..2f607b0 100644
--- a/twl/src/de/matthiasmann/twl/renderer/AnimationState.java
+++ b/twl/src/de/matthiasmann/twl/renderer/AnimationState.java
@@ -29,6 +29,7 @@
*/
package de.matthiasmann.twl.renderer;
+import java.util.ArrayList;
import java.util.HashMap;
/**
@@ -75,6 +76,8 @@ public static final class StateKey {
private static final HashMap keys =
new HashMap();
+ private static final ArrayList keysByID =
+ new ArrayList();
private StateKey(String name, int id) {
this.name = name;
@@ -131,9 +134,20 @@ public synchronized static StateKey get(String name) {
if(key == null) {
key = new StateKey(name, keys.size());
keys.put(name, key);
+ keysByID.add(key);
}
return key;
}
+
+ /**
+ * Returns the StateKey for the specified id.
+ * @param id the ID to lookup
+ * @return the StateKey
+ * @throws IndexOutOfBoundsException if the ID is invalid
+ */
+ public synchronized static StateKey get(int id) {
+ return keysByID.get(id);
+ }
public synchronized static int getNumStateKeys() {
return keys.size();
diff --git a/twl/src/de/matthiasmann/twl/renderer/AnimationStateString.java b/twl/src/de/matthiasmann/twl/renderer/AnimationStateString.java
new file mode 100644
index 0000000..a60436e
--- /dev/null
+++ b/twl/src/de/matthiasmann/twl/renderer/AnimationStateString.java
@@ -0,0 +1,17 @@
+package de.matthiasmann.twl.renderer;
+
+import de.matthiasmann.twl.AnimationState;
+import de.matthiasmann.twl.renderer.AnimationState.StateKey;
+
+public class AnimationStateString extends AnimationState {
+ String stateKey;
+
+ public AnimationStateString(String key)
+ {
+ stateKey = key;
+ }
+
+ public boolean getAnimationState(StateKey stateKey) {
+ return stateKey.getName().contains(this.stateKey); // No, this might fuck up sometimes. But who cares, it works for what I'm doing.
+ }
+}
diff --git a/twl/src/de/matthiasmann/twl/renderer/AttributedString.java b/twl/src/de/matthiasmann/twl/renderer/AttributedString.java
new file mode 100644
index 0000000..af84c7e
--- /dev/null
+++ b/twl/src/de/matthiasmann/twl/renderer/AttributedString.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (c) 2008-2011, Matthias Mann
+ *
+ * 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 Matthias Mann 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 de.matthiasmann.twl.renderer;
+
+/**
+ * An attribute string which also works as an iterator.
+ *
+ * @author Matthias Mann
+ */
+public interface AttributedString extends CharSequence, AnimationState {
+
+ /**
+ * Returns the current position of the attribute iterator
+ * @return the current position
+ */
+ public int getPosition();
+
+ /**
+ * Changes the current position.
+ * @param pos the new position
+ * @throws IllegalArgumentException when {@code pos} is < 0 or > length()
+ */
+ public void setPosition(int pos);
+
+ /**
+ * Moves the current position forward to the next segment.
+ * @return the new position
+ */
+ public int advance();
+}
diff --git a/twl/src/de/matthiasmann/twl/renderer/AttributedStringFontCache.java b/twl/src/de/matthiasmann/twl/renderer/AttributedStringFontCache.java
new file mode 100644
index 0000000..f2c3f44
--- /dev/null
+++ b/twl/src/de/matthiasmann/twl/renderer/AttributedStringFontCache.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (c) 2008-2011, Matthias Mann
+ *
+ * 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 Matthias Mann 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 de.matthiasmann.twl.renderer;
+
+/**
+ *
+ * @author Matthias Mann
+ */
+public interface AttributedStringFontCache extends Resource {
+
+ /**
+ * Returns the width in pixels of the cached text block
+ * @return the width in pixels of the cached text block
+ */
+ public int getWidth();
+
+ /**
+ * Returns the height in pixels of the cached text block
+ * @return the height in pixels of the cached text block
+ */
+ public int getHeight();
+
+ /**
+ * Draw the cached text block at the given coordinates
+ * @param x the left coordinate
+ * @param y the top coordinate
+ */
+ public void draw(int x, int y);
+}
diff --git a/twl/src/de/matthiasmann/twl/renderer/DynamicImage.java b/twl/src/de/matthiasmann/twl/renderer/DynamicImage.java
index 40a1a97..eb0f347 100644
--- a/twl/src/de/matthiasmann/twl/renderer/DynamicImage.java
+++ b/twl/src/de/matthiasmann/twl/renderer/DynamicImage.java
@@ -59,6 +59,17 @@ public enum Format {
*/
public void update(ByteBuffer data, Format format);
+ /**
+ * Updates the complete image.
+ *
+ * @param data The new texels
+ * @param stride The number of bytes from one row to the next
+ * @param format The format of the texel data
+ * @throws IllegalArgumentException if the ByteBuffer does not contain enough data
+ * or the stride is not a multiple of the bytes per pixel of specifed format
+ */
+ public void update(ByteBuffer data, int stride, Format format);
+
/**
* Updates a region of the image with new data.
*
@@ -72,5 +83,21 @@ public enum Format {
* or the ByteBuffer does not contain enough data
*/
public void update(int xoffset, int yoffset, int width, int height, ByteBuffer data, Format format);
+
+ /**
+ * Updates a region of the image with new data.
+ *
+ * @param xoffset Specifies a texel offset in the x direction within the image
+ * @param yoffset Specifies a texel offset in the y direction within the image
+ * @param width Specifies the width of the update area
+ * @param height Specifies the height of the update area
+ * @param data The new texels
+ * @param stride The number of bytes from one row to the next
+ * @param format The format of the texel data
+ * @throws IllegalArgumentException if the update area is not within the image bounds
+ * or the ByteBuffer does not contain enough data
+ * or the stride is not a multiple of the bytes per pixel of specifed format
+ */
+ public void update(int xoffset, int yoffset, int width, int height, ByteBuffer data, int stride, Format format);
}
diff --git a/twl/src/de/matthiasmann/twl/renderer/Font.java b/twl/src/de/matthiasmann/twl/renderer/Font.java
index 03ab938..886f532 100644
--- a/twl/src/de/matthiasmann/twl/renderer/Font.java
+++ b/twl/src/de/matthiasmann/twl/renderer/Font.java
@@ -38,6 +38,12 @@
*/
public interface Font extends Resource {
+ /**
+ * Returns true if the font is proportional or false if it's fixed width.
+ * @return true if the font is proportional
+ */
+ boolean isProportional();
+
/**
* Returns the base line of the font measured in pixels from the top of the text bounding box
* @return the base line of the font measured in pixels from the top of the text bounding box
@@ -50,6 +56,10 @@ public interface Font extends Resource {
*/
int getLineHeight();
+ /**
+ * Returns the width of a ' '
+ * @return the width of a ' '
+ */
int getSpaceWidth();
/**
@@ -100,7 +110,7 @@ public interface Font extends Resource {
int computeVisibleGlpyhs(CharSequence str, int start, int end, int width);
/**
- * Draws multi line text - lines are spliited at '\n'
+ * Draws multi line text - lines are splitted at '\n'
* @param as A time source for animation - may be null
* @param x left coordinate of the text block
* @param y top coordinate of the text block
diff --git a/twl/src/de/matthiasmann/twl/renderer/Font2.java b/twl/src/de/matthiasmann/twl/renderer/Font2.java
new file mode 100644
index 0000000..4739cfb
--- /dev/null
+++ b/twl/src/de/matthiasmann/twl/renderer/Font2.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (c) 2008-2011, Matthias Mann
+ *
+ * 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 Matthias Mann 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 de.matthiasmann.twl.renderer;
+
+/**
+ * Extended font rendering API
+ *
+ * @author Matthias Mann
+ */
+public interface Font2 extends Font {
+
+ int drawText(int x, int y, AttributedString attributedString);
+
+ int drawText(int x, int y, AttributedString attributedString, int start, int end);
+
+ void drawMultiLineText(int x, int y, AttributedString attributedString);
+
+ void drawMultiLineText(int x, int y, AttributedString attributedString, int start, int end);
+
+ AttributedStringFontCache cacheText(AttributedStringFontCache prevCache, AttributedString attributedString);
+
+ AttributedStringFontCache cacheText(AttributedStringFontCache prevCache, AttributedString attributedString, int start, int end);
+
+ AttributedStringFontCache cacheMultiLineText(AttributedStringFontCache prevCache, AttributedString attributedString);
+
+ AttributedStringFontCache cacheMultiLineText(AttributedStringFontCache prevCache, AttributedString attributedString, int start, int end);
+
+}
diff --git a/twl/src/de/matthiasmann/twl/renderer/FontMapper.java b/twl/src/de/matthiasmann/twl/renderer/FontMapper.java
new file mode 100644
index 0000000..cc95163
--- /dev/null
+++ b/twl/src/de/matthiasmann/twl/renderer/FontMapper.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright (c) 2008-2012, Matthias Mann
+ *
+ * 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 Matthias Mann 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 de.matthiasmann.twl.renderer;
+
+import de.matthiasmann.twl.utils.StateSelect;
+import de.matthiasmann.twl.utils.StringList;
+import java.io.IOException;
+import java.net.URL;
+
+/**
+ * A font mapper which tries to retrieve the closest font for the specified parameters
+ *
+ * @author Matthias Mann
+ */
+public interface FontMapper extends Resource {
+
+ public static final int STYLE_NORMAL = 0;
+ public static final int STYLE_BOLD = 1;
+ public static final int STYLE_ITALIC = 2;
+
+ /**
+ * Register this font is as a weak font.
+ *
+ *
If a font is already registered for the specified
+ * fontFamily and style then this registration is ignored.
+ *
If a non weak font is registered for the specified
+ * fontFamily and style then an existing weak font is replaced.
+ */
+ public static final int REGISTER_WEAK = 256;
+
+ /**
+ * Retrive the cloest font for the given parameters
+ *
+ * @param fontFamilies a list of family names with decreasing piority
+ * @param fontSize the desired font size in pixels
+ * @param style a combination of the STYLE_* flags
+ * @param select the StateSelect object
+ * @param fontParams the font parameters - must be exactly 1 more then
+ * the number of expressions in the select object
+ * @return the Font object or {@code null} if the font could not be found
+ * @throws NullPointerException when one of the parameters is null
+ * @throws IllegalArgumentException when the number of font parameters doesn't match the number of state expressions
+ */
+ public Font getFont(StringList fontFamilies, int fontSize, int style,
+ StateSelect select, FontParameter ... fontParams);
+
+ /**
+ * Registers a font file
+ *
+ * @param fontFamily the font family for which to register the font
+ * @param style a combination of the STYLE_* and REGISTER_* flags
+ * @param url the URL for the font file
+ * @return true if the specified font could be registered
+ */
+ public boolean registerFont(String fontFamily, int style, URL url);
+
+ /**
+ * Registers a font file and determines the style from the font itself.
+ *
+ * @param fontFamily the font family for which to register the font
+ * @param url the URL for the font file
+ * @return true if the specified font could be registered
+ * @throws IOException when the font could not be parsed
+ */
+ public boolean registerFont(String fontFamily, URL url) throws IOException;
+}
diff --git a/twl/src/de/matthiasmann/twl/renderer/FontParameter.java b/twl/src/de/matthiasmann/twl/renderer/FontParameter.java
index f1dc743..00561ff 100644
--- a/twl/src/de/matthiasmann/twl/renderer/FontParameter.java
+++ b/twl/src/de/matthiasmann/twl/renderer/FontParameter.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2008-2009, Matthias Mann
+ * Copyright (c) 2008-2012, Matthias Mann
*
* All rights reserved.
*
@@ -29,35 +29,187 @@
*/
package de.matthiasmann.twl.renderer;
-import de.matthiasmann.twl.utils.StateExpression;
-import java.util.Map;
+import de.matthiasmann.twl.Color;
+import java.util.HashMap;
/**
- *
+ * An extensible typed map for font parameters.
+ *
* @author Matthias Mann
*/
-public class FontParameter {
+public final class FontParameter {
+
+ static final HashMap> parameterMap = new HashMap>();
- private final StateExpression condition;
- private final Map params;
+ public static final Parameter COLOR = newParameter("color", Color.WHITE);
+ public static final Parameter UNDERLINE = newParameter("underline", false);
+ public static final Parameter LINETHROUGH = newParameter("linethrough", false);
+
+ private Object[] values;
- public FontParameter(StateExpression condition, Map params) {
- if(condition == null) {
- throw new NullPointerException("condition");
- }
- if(params == null) {
- throw new NullPointerException("params");
- }
- this.condition = condition;
- this.params = params;
+ public FontParameter() {
+ this.values = new Object[8];
}
- public StateExpression getCondition() {
- return condition;
+ public FontParameter(FontParameter base) {
+ this.values = base.values.clone();
}
- public Map getParams() {
- return params;
+ /**
+ * Sets a parameter value
+ * @param the type of the parameter
+ * @param param the parameter
+ * @param value the value or null to revert to it's default value
+ */
+ public void put(Parameter param, T value) {
+ if(param == null) {
+ throw new NullPointerException("type");
+ }
+ if(value != null && !param.dataClass.isInstance(value)) {
+ throw new ClassCastException("value");
+ }
+ int ordinal = param.ordinal;
+ int curLength = values.length;
+ if(ordinal >= curLength) {
+ Object[] tmp = new Object[Math.max(ordinal + 1, curLength*2)];
+ System.arraycopy(values, 0, tmp, 0, curLength);
+ values = tmp;
+ }
+ values[ordinal] = value;
+ }
+
+ /**
+ * Returns the value of the specified parameter
+ * @param the type of the parameter
+ * @param param the parameter
+ * @return the parameter value or it's default value when the parameter was not set
+ */
+ public T get(Parameter param) {
+ if(param.ordinal < values.length) {
+ Object raw = values[param.ordinal];
+ if(raw != null) {
+ return param.dataClass.cast(raw);
+ }
+ }
+ return param.defaultValue;
+ }
+
+ /**
+ * Returns an array of all registered parameter
+ * @return an array of all registered parameter
+ */
+ public static Parameter[] getRegisteredParameter() {
+ synchronized (parameterMap) {
+ return parameterMap.values().toArray(new Parameter>[parameterMap.size()]);
+ }
+ }
+
+ /**
+ * Returns the parameter instance for the given name
+ * @param name the name to look up
+ * @return the parameter instance or null when the name is not registered
+ */
+ public static Parameter> getParameter(String name) {
+ synchronized (parameterMap) {
+ return parameterMap.get(name);
+ }
+ }
+
+ /**
+ * Registers a new parameter.
+ *
+ *
The data class is extracted from the default value.
+ *
If the name is already registered then the existing parameter is returned.
+ *
+ * @param the data type of the parameter
+ * @param name the parameter name
+ * @param defaultValue the default value
+ * @return the parameter instance
+ * @throws NullPointerException when one of the parameters is null
+ * @throws IllegalStateException when the name is already registered but with
+ * different dataClass or defaultValue
+ */
+ public static Parameter newParameter(String name, T defaultValue) {
+ if(defaultValue == null) {
+ throw new NullPointerException("defaultValue");
+ }
+ @SuppressWarnings("unchecked")
+ Class dataClass = (Class)defaultValue.getClass();
+ return newParameter(name, dataClass, defaultValue);
+ }
+
+ /**
+ * Registers a new parameter.
+ *
+ *
If the name is already registered then the existing parameter is returned.
+ *
+ * @param the data type of the parameter
+ * @param name the parameter name
+ * @param dataClass the data class
+ * @param defaultValue the default value - can be null.
+ * @return the parameter instance
+ * @throws NullPointerException when name or dataClass is null
+ * @throws IllegalStateException when the name is already registered but with
+ * different dataClass or defaultValue
+ */
+ public static Parameter newParameter(String name, Class dataClass, T defaultValue) {
+ if(name == null) {
+ throw new NullPointerException("name");
+ }
+ if(dataClass == null) {
+ throw new NullPointerException("dataClass");
+ }
+
+ synchronized (parameterMap) {
+ Parameter> existing = parameterMap.get(name);
+ if(existing != null) {
+ if(existing.dataClass != dataClass || !equals(existing.defaultValue, defaultValue)) {
+ throw new IllegalStateException("type '" + name + "' already registered but different");
+ }
+
+ @SuppressWarnings("unchecked")
+ Parameter type = (Parameter)existing;
+ return type;
+ }
+
+ Parameter type = new Parameter(name, dataClass, defaultValue, parameterMap.size());
+ parameterMap.put(name, type);
+ return type;
+ }
+ }
+
+ private static boolean equals(Object a, Object b) {
+ return (a == b) || (a != null && a.equals(b));
}
+ public static final class Parameter {
+ final String name;
+ final Class dataClass;
+ final T defaultValue;
+ final int ordinal;
+
+ Parameter(String name, Class dataClass, T defaultValue, int ordinal) {
+ this.name = name;
+ this.dataClass = dataClass;
+ this.defaultValue = defaultValue;
+ this.ordinal = ordinal;
+ }
+
+ public final String getName() {
+ return name;
+ }
+
+ public final Class getDataClass() {
+ return dataClass;
+ }
+
+ public final T getDefaultValue() {
+ return defaultValue;
+ }
+
+ @Override
+ public String toString() {
+ return ordinal + ":" + name + ":" + dataClass.getSimpleName();
+ }
+ }
}
diff --git a/twl/src/de/matthiasmann/twl/renderer/Gradient.java b/twl/src/de/matthiasmann/twl/renderer/Gradient.java
new file mode 100644
index 0000000..e497f0e
--- /dev/null
+++ b/twl/src/de/matthiasmann/twl/renderer/Gradient.java
@@ -0,0 +1,131 @@
+/*
+ * Copyright (c) 2008-2012, Matthias Mann
+ *
+ * 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 Matthias Mann 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 de.matthiasmann.twl.renderer;
+
+import de.matthiasmann.twl.Color;
+import java.util.ArrayList;
+
+/**
+ * Info class used to construct a gradient image
+ *
+ * @author Matthias Mann
+ * @see Renderer#createGradient(de.matthiasmann.twl.renderer.Gradient)
+ */
+public class Gradient {
+
+ public enum Type {
+ HORIZONTAL,
+ VERTICAL
+ }
+
+ public enum Wrap {
+ SCALE,
+ CLAMP,
+ REPEAT,
+ MIRROR
+ }
+
+ private final Type type;
+ private Wrap wrap;
+ private final ArrayList stops;
+
+ public Gradient(Type type) {
+ if(type == null) {
+ throw new NullPointerException("type");
+ }
+ this.type = type;
+ this.wrap = Wrap.SCALE;
+ this.stops = new ArrayList();
+ }
+
+ public Type getType() {
+ return type;
+ }
+
+ public void setWrap(Wrap wrap) {
+ if(wrap == null) {
+ throw new NullPointerException("wrap");
+ }
+ this.wrap = wrap;
+ }
+
+ public Wrap getWrap() {
+ return wrap;
+ }
+
+ public int getNumStops() {
+ return stops.size();
+ }
+
+ public Stop getStop(int index) {
+ return stops.get(index);
+ }
+
+ public Stop[] getStops() {
+ return stops.toArray(new Stop[stops.size()]);
+ }
+
+ public void addStop(float pos, Color color) {
+ if(color == null) {
+ throw new NullPointerException("color");
+ }
+ int numStops = stops.size();
+ if(numStops == 0) {
+ if(!(pos >= 0)) {
+ throw new IllegalArgumentException("first stop must be >= 0.0f");
+ }
+ if(pos > 0) {
+ stops.add(new Stop(0.0f, color));
+ }
+ }
+ if(numStops > 0 && !(pos > stops.get(numStops-1).pos)) {
+ throw new IllegalArgumentException("pos must be monotone increasing");
+ }
+ stops.add(new Stop(pos, color));
+ }
+
+ public static class Stop {
+ final float pos;
+ final Color color;
+
+ public Stop(float pos, Color color) {
+ this.pos = pos;
+ this.color = color;
+ }
+
+ public float getPos() {
+ return pos;
+ }
+
+ public Color getColor() {
+ return color;
+ }
+ }
+}
diff --git a/twl/src/de/matthiasmann/twl/renderer/OffscreenRenderer.java b/twl/src/de/matthiasmann/twl/renderer/OffscreenRenderer.java
new file mode 100644
index 0000000..81a9a82
--- /dev/null
+++ b/twl/src/de/matthiasmann/twl/renderer/OffscreenRenderer.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright (c) 2008-2012, Matthias Mann
+ *
+ * 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 Matthias Mann 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 de.matthiasmann.twl.renderer;
+
+import de.matthiasmann.twl.Widget;
+
+/**
+ * An interface to allow offscreen rendering for special effects
+ *
+ * @author Matthias Mann
+ */
+public interface OffscreenRenderer {
+
+ /**
+ * Starts offscreen rendering. All following rendering operations will render
+ * into the returned offscreen surface. Rendering outside the specified area
+ * will be ignored.
+ *
+ * @param widget the widget which will render to the returned surface - can be null.
+ * @param oldSurface the previous offscreen surface to reuse / overwrite
+ * @param x the X coordinate of the region, can be negative.
+ * @param y the Y coordinate of the region, can be negative.
+ * @param width the width, can be larger then the screen size
+ * @param height the height, can be larger then the screen size
+ * @return the OffscreenSurface or null if offscreen rendering could not be started.
+ */
+ public OffscreenSurface startOffscreenRendering(Widget widget,
+ OffscreenSurface oldSurface, int x, int y, int width, int height);
+
+ /**
+ * Ends the current offscreen rendering.
+ * Only call this method after a sucessful call of
+ * {@link #startOffscreenRendering(de.matthiasmann.twl.renderer.OffscreenSurface, int, int, int, int) }
+ */
+ public void endOffscreenRendering();
+}
diff --git a/twl/src/de/matthiasmann/twl/renderer/OffscreenSurface.java b/twl/src/de/matthiasmann/twl/renderer/OffscreenSurface.java
new file mode 100644
index 0000000..612cbe0
--- /dev/null
+++ b/twl/src/de/matthiasmann/twl/renderer/OffscreenSurface.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (c) 2008-2011, Matthias Mann
+ *
+ * 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 Matthias Mann 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 de.matthiasmann.twl.renderer;
+
+/**
+ * An offscreen surface.
+ *
+ *
It exposes all Image methods, in order to do special effects
+ * you need to cast this to the implementation
.
+ *
+ * @author Matthias Mann
+ */
+public interface OffscreenSurface extends Resource, Image {
+
+}
diff --git a/twl/src/de/matthiasmann/twl/renderer/Renderer.java b/twl/src/de/matthiasmann/twl/renderer/Renderer.java
index bd86713..e03edc5 100644
--- a/twl/src/de/matthiasmann/twl/renderer/Renderer.java
+++ b/twl/src/de/matthiasmann/twl/renderer/Renderer.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2008-2010, Matthias Mann
+ * Copyright (c) 2008-2012, Matthias Mann
*
* All rights reserved.
*
@@ -31,10 +31,9 @@
import de.matthiasmann.twl.Event;
import de.matthiasmann.twl.Rect;
+import de.matthiasmann.twl.utils.StateSelect;
import java.io.IOException;
import java.net.URL;
-import java.util.Collection;
-import java.util.Map;
/**
* TWL Rendering interface
@@ -56,11 +55,11 @@ public interface Renderer {
* must be called.
* @return true if rendering was started, false otherwise
*/
- public boolean startRenderering();
+ public boolean startRendering();
/**
* Clean up after rendering TWL.
- * Only call this method when {@link #startRenderering()} returned {@code true}
+ * Only call this method when {@link #startRendering()} returned {@code true}
*/
public void endRendering();
@@ -106,12 +105,15 @@ public interface Renderer {
* Loads a font.
*
* @param baseUrl the base URL that can be used to load font data
- * @param parameter font parameter
- * @param conditionalParameter conditional font paramters - evaluate in order based on AnimationState
+ * @param select the StateSelect object
+ * @param parameterList the font parameters - must be exactly 1 more then
+ * the number of expressions in the select object
* @return a Font object
* @throws java.io.IOException if the font could not be loaded
+ * @throws NullPointerException when one of the parameters is null
+ * @throws IllegalArgumentException when the number of font parameters doesn't match the number of state expressions
*/
- public Font loadFont(URL baseUrl, Map parameter, Collection conditionalParameter) throws IOException;
+ public Font loadFont(URL baseUrl, StateSelect select, FontParameter ... parameterList) throws IOException;
/**
* Loads a texture. Textures are used to create images.
@@ -133,6 +135,24 @@ public interface Renderer {
*/
public LineRenderer getLineRenderer();
+ /**
+ * Returns the offscreen renderer. If offscreen rendering is not supported then this method returns null.
+ *
+ * This is an optional operation.
+ *
+ * @return the offscreen renderer or null if not supported.
+ */
+ public OffscreenRenderer getOffscreenRenderer();
+
+ /**
+ * Returns the font mapper object if one is available.
+ *
+ * This is an optional operation.
+ *
+ * @return the font mapper or null if not supported.
+ */
+ public FontMapper getFontMapper();
+
/**
* Creates a dynamic image with undefined content.
*
@@ -143,13 +163,45 @@ public interface Renderer {
* @return a new dynamic image or null if the image could not be created
*/
public DynamicImage createDynamicImage(int width, int height);
-
+
+ public Image createGradient(Gradient gradient);
+
/**
- * Sets the clipping area for all rendering operations.
- * @param rect A rectangle or null to disable clipping.
+ * Enters a clip region.
+ *
+ * The new clip region is the intersection of the current clip region with
+ * the specified coordinates.
+ *
+ * @param x the left edge
+ * @param y the top edge
+ * @param w the width
+ * @param h the height
*/
- public void setClipRect(Rect rect);
-
+ public void clipEnter(int x, int y, int w, int h);
+
+ /**
+ * Enters a clip region.
+ *
+ * The new clip region is the intersection of the current clip region with
+ * the specified coordinates.
+ *
+ * @param rect the coordinates
+ */
+ public void clipEnter(Rect rect);
+
+ /**
+ * Checks if the active clip region is empty (nothing will render).
+ * @return true if the active clip region is empty
+ */
+ public boolean clipIsEmpty();
+
+ /**
+ * Leaves a clip region creeated by {@code #clipEnter}
+ * @see #clipEnter(int, int, int, int)
+ * @see #clipEnter(de.matthiasmann.twl.Rect)
+ */
+ public void clipLeave();
+
public void setCursor(MouseCursor cursor);
/**
diff --git a/twl/src/de/matthiasmann/twl/renderer/Texture.java b/twl/src/de/matthiasmann/twl/renderer/Texture.java
index dcc4c8e..34952b6 100644
--- a/twl/src/de/matthiasmann/twl/renderer/Texture.java
+++ b/twl/src/de/matthiasmann/twl/renderer/Texture.java
@@ -57,11 +57,12 @@ public interface Texture extends Resource {
* @param width width in pixels of the image - if negative the image is horizontaly flipped
* @param height height in pixels of the image - if negative the image is vertically flipped
* @param tintColor the tintColor - maybe null
- * @param tiled truw if this image should do tiled rendering
+ * @param tiled true if this image should do tiled rendering
+ * @param rotation the rotation to apply to this sub section while rendering
* @see Image#createTintedVersion(de.matthiasmann.twl.Color)
* @return an image object
*/
- public Image getImage(int x, int y, int width, int height, Color tintColor, boolean tiled);
+ public Image getImage(int x, int y, int width, int height, Color tintColor, boolean tiled, Rotation rotation);
public MouseCursor createCursor(int x, int y, int width, int height, int hotSpotX, int hotSpotY, Image imageRef);
@@ -69,4 +70,11 @@ public interface Texture extends Resource {
* After calling this function getImage() and createCursor() may fail to work
*/
public void themeLoadingDone();
+
+ public enum Rotation {
+ NONE,
+ CLOCKWISE_90,
+ CLOCKWISE_180,
+ CLOCKWISE_270
+ }
}
diff --git a/twl/src/de/matthiasmann/twl/renderer/lwjgl/BitmapFont.java b/twl/src/de/matthiasmann/twl/renderer/lwjgl/BitmapFont.java
index b1d0395..e00fdf6 100644
--- a/twl/src/de/matthiasmann/twl/renderer/lwjgl/BitmapFont.java
+++ b/twl/src/de/matthiasmann/twl/renderer/lwjgl/BitmapFont.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2008-2010, Matthias Mann
+ * Copyright (c) 2008-2011, Matthias Mann
*
* All rights reserved.
*
@@ -40,13 +40,16 @@
import java.io.InputStreamReader;
import java.io.Reader;
import java.net.URL;
+import java.nio.FloatBuffer;
import java.util.HashMap;
import org.lwjgl.opengl.GL11;
+import org.lwjgl.opengl.Util;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
/**
- * A Bitmap Font class. Parses the output of AngelCode's BMFont tool.
+ * A Bitmap Font class. Renders fonts generated by TWL Theme Editor's
+ * "Create font" tool.
*
* @author Matthias Mann
*/
@@ -63,12 +66,21 @@ static class Glyph extends TextureAreaBase {
byte[][] kerning;
public Glyph(int x, int y, int width, int height, int texWidth, int texHeight) {
- super(x, y, width, height, texWidth, texHeight);
+ super(x, y, (height <= 0) ? 0 : width, height, texWidth, texHeight);
}
void draw(int x, int y) {
drawQuad(x+xoffset, y+yoffset, width, height);
}
+
+ void draw(FloatBuffer va, int x, int y) {
+ x += xoffset;
+ y += yoffset;
+ va.put(tx0).put(ty0).put(x ).put(y);
+ va.put(tx0).put(ty1).put(x ).put(y + height);
+ va.put(tx1).put(ty1).put(x + width).put(y + height);
+ va.put(tx1).put(ty0).put(x + width).put(y);
+ }
int getKerning(char ch) {
if(kerning != null) {
@@ -98,6 +110,7 @@ void setKerning(int ch, int value) {
private final int baseLine;
private final int spaceWidth;
private final int ex;
+ private final boolean proportional;
public BitmapFont(LWJGLRenderer renderer, XMLParser xmlp, URL baseUrl) throws XmlPullParserException, IOException {
xmlp.require(XmlPullParser.START_TAG, null, "font");
@@ -128,7 +141,9 @@ public BitmapFont(LWJGLRenderer renderer, XMLParser xmlp, URL baseUrl) throws Xm
throw new UnsupportedOperationException("only page id 0 supported");
}
String textureName = xmlp.getAttributeValue(null, "file");
+ Util.checkGLError();
this.texture = renderer.load(new URL(baseUrl, textureName), LWJGLTexture.Format.ALPHA, LWJGLTexture.Filter.NEAREST);
+ Util.checkGLError();
xmlp.nextTag();
xmlp.require(XmlPullParser.END_TAG, null, "page");
xmlp.nextTag();
@@ -138,6 +153,9 @@ public BitmapFont(LWJGLRenderer renderer, XMLParser xmlp, URL baseUrl) throws Xm
xmlp.ignoreOtherAttributes();
xmlp.nextTag();
+ int firstXAdvance = Integer.MIN_VALUE;
+ boolean prop = true;
+
glyphs = new Glyph[PAGES][];
while(!xmlp.isEndTag()) {
xmlp.require(XmlPullParser.START_TAG, null, "char");
@@ -158,6 +176,13 @@ public BitmapFont(LWJGLRenderer renderer, XMLParser xmlp, URL baseUrl) throws Xm
xmlp.nextTag();
xmlp.require(XmlPullParser.END_TAG, null, "char");
xmlp.nextTag();
+ if(g.xadvance != firstXAdvance && g.xadvance > 0) {
+ if(firstXAdvance == Integer.MIN_VALUE) {
+ firstXAdvance = g.xadvance;
+ } else {
+ prop = false;
+ }
+ }
}
xmlp.require(XmlPullParser.END_TAG, null, "chars");
@@ -186,6 +211,8 @@ public BitmapFont(LWJGLRenderer renderer, XMLParser xmlp, URL baseUrl) throws Xm
Glyph gx = getGlyph('x');
ex = (gx != null) ? gx.height : 1;
+
+ proportional = prop;
}
public BitmapFont(LWJGLRenderer renderer, Reader reader, URL baseUrl) throws IOException {
@@ -210,6 +237,8 @@ public BitmapFont(LWJGLRenderer renderer, Reader reader, URL baseUrl) throws IOE
this.glyphs = new Glyph[PAGES][];
parseFntLine(parseFntLine(br, "chars"), params);
int charCount = parseInt(params, "count");
+ int firstXAdvance = Integer.MIN_VALUE;
+ boolean prop = true;
for(int charIdx=0 ; charIdx 0) {
+ if(firstXAdvance == Integer.MIN_VALUE) {
+ firstXAdvance = g.xadvance;
+ } else {
+ prop = false;
+ }
+ }
}
parseFntLine(parseFntLine(br, "kernings"), params);
int kerningCount = parseInt(params, "count");
@@ -241,6 +278,8 @@ public BitmapFont(LWJGLRenderer renderer, Reader reader, URL baseUrl) throws IOE
Glyph gx = getGlyph('x');
ex = (gx != null) ? gx.height : 1;
+
+ this.proportional = prop;
}
public static BitmapFont loadFont(LWJGLRenderer renderer, URL url) throws IOException {
@@ -251,6 +290,7 @@ public static BitmapFont loadFont(LWJGLRenderer renderer, URL url) throws IOExce
xmlp.require(XmlPullParser.START_DOCUMENT, null, null);
xmlp.nextTag();
startTagSeen = true;
+ Util.checkGLError();
return new BitmapFont(renderer, xmlp, url);
} finally {
xmlp.close();
@@ -268,6 +308,10 @@ public static BitmapFont loadFont(LWJGLRenderer renderer, URL url) throws IOExce
}
}
}
+
+ public boolean isProportional() {
+ return proportional;
+ }
public int getBaseLine() {
return baseLine;
@@ -313,7 +357,7 @@ private void addKerning(int first, int second, int amount) {
}
}
- private Glyph getGlyph(char ch) {
+ final Glyph getGlyph(char ch) {
Glyph[] page = glyphs[ch >> LOG2_PAGE_SIZE];
if(page != null) {
return page[ch & (PAGE_SIZE-1)];
@@ -355,10 +399,17 @@ public int computeVisibleGlpyhs(CharSequence str, int start, int end, int availW
width += lastGlyph.getKerning(ch);
}
lastGlyph = g;
- if(width + g.width + g.xoffset > availWidth) {
- break;
+ if(proportional) {
+ width += g.xadvance;
+ if(width > availWidth) {
+ break;
+ }
+ } else {
+ if(width + g.width + g.xoffset > availWidth) {
+ break;
+ }
+ width += g.xadvance;
}
- width += g.xadvance;
}
}
return index - start;
@@ -370,7 +421,9 @@ protected int drawText(int x, int y, CharSequence str, int start, int end) {
while(start < end) {
lastGlyph = getGlyph(str.charAt(start++));
if(lastGlyph != null) {
- lastGlyph.draw(x, y);
+ if(lastGlyph.width > 0) {
+ lastGlyph.draw(x, y);
+ }
x += lastGlyph.xadvance;
break;
}
@@ -381,7 +434,9 @@ protected int drawText(int x, int y, CharSequence str, int start, int end) {
if(g != null) {
x += lastGlyph.getKerning(ch);
lastGlyph = g;
- g.draw(x, y);
+ if(g.width > 0) {
+ g.draw(x, y);
+ }
x += g.xadvance;
}
}
@@ -514,6 +569,10 @@ public FontCache cacheText(LWJGLFontCache cache, CharSequence str, int start, in
return null;
}
+ boolean bind() {
+ return texture.bind();
+ }
+
protected boolean prepare() {
if(texture.bind()) {
GL11.glBegin(GL11.GL_QUADS);
diff --git a/twl/src/de/matthiasmann/twl/renderer/lwjgl/GradientImage.java b/twl/src/de/matthiasmann/twl/renderer/lwjgl/GradientImage.java
new file mode 100644
index 0000000..b1df4fd
--- /dev/null
+++ b/twl/src/de/matthiasmann/twl/renderer/lwjgl/GradientImage.java
@@ -0,0 +1,233 @@
+/*
+ * Copyright (c) 2008-2012, Matthias Mann
+ *
+ * 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 Matthias Mann 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 de.matthiasmann.twl.renderer.lwjgl;
+
+import de.matthiasmann.twl.Color;
+import de.matthiasmann.twl.renderer.AnimationState;
+import de.matthiasmann.twl.renderer.Gradient;
+import de.matthiasmann.twl.renderer.Gradient.Stop;
+import de.matthiasmann.twl.renderer.Gradient.Type;
+import de.matthiasmann.twl.renderer.Gradient.Wrap;
+import de.matthiasmann.twl.renderer.Image;
+import org.lwjgl.opengl.GL11;
+
+/**
+ *
+ * @author Matthias Mann
+ */
+public class GradientImage implements Image {
+
+ private final LWJGLRenderer renderer;
+ private final Type type;
+ private final Wrap wrap;
+ private final Stop[] stops;
+ private final Color tint;
+ private final float endPos;
+
+ GradientImage(GradientImage src, Color tint) {
+ this.renderer = src.renderer;
+ this.type = src.type;
+ this.wrap = src.wrap;
+ this.stops = src.stops;
+ this.endPos = src.endPos;
+ this.tint = tint;
+ }
+
+ public GradientImage(LWJGLRenderer renderer, Gradient gradient) {
+ if(gradient == null) {
+ throw new NullPointerException("gradient");
+ }
+ if(gradient.getNumStops() < 1) {
+ throw new IllegalArgumentException("Need at least 1 stop for a gradient");
+ }
+
+ this.renderer = renderer;
+ this.type = gradient.getType();
+ this.tint = Color.WHITE;
+ if(gradient.getNumStops() == 1) {
+ Color color = gradient.getStop(0).getColor();
+ wrap = Wrap.SCALE;
+ stops = new Stop[] {
+ new Stop(0.0f, color),
+ new Stop(1.0f, color)
+ };
+ endPos = 1.0f;
+ } else if(gradient.getWrap() == Wrap.MIRROR) {
+ int numStops = gradient.getNumStops();
+ wrap = Wrap.REPEAT;
+ stops = new Stop[numStops*2-1];
+ for(int i=0 ; i=0 ; i++,j--) {
+ stops[i] = new Stop(endPos - stops[j].getPos(), stops[j].getColor());
+ }
+ } else {
+ wrap = gradient.getWrap();
+ stops = gradient.getStops();
+ endPos = stops[stops.length-1].getPos();
+ }
+ }
+
+
+ public Image createTintedVersion(Color color) {
+ return new GradientImage(this, tint.multiply(color));
+ }
+
+ private boolean isHorz() {
+ return type == Type.HORIZONTAL;
+ }
+
+ private int getLastPos() {
+ return Math.round(stops[stops.length-1].getPos());
+ }
+
+ public int getHeight() {
+ return isHorz() ? 1 : getLastPos();
+ }
+
+ public int getWidth() {
+ return isHorz() ? getLastPos() : 1;
+ }
+
+ public void draw(AnimationState as, int x, int y) {
+ if(isHorz()) {
+ drawHorz(x, y, getLastPos(), 1);
+ } else {
+ drawVert(x, y, 1, getLastPos());
+ }
+ }
+
+ public void draw(AnimationState as, int x, int y, int width, int height) {
+ if(isHorz()) {
+ drawHorz(x, y, width, height);
+ } else {
+ drawVert(x, y, width, height);
+ }
+ }
+
+ private void drawHorz(int x, int y, int width, int height) {
+ if(width <= 0 || height <= 0) {
+ return;
+ }
+ TintStack tintStack = renderer.tintStack.push(tint);
+ GL11.glDisable(GL11.GL_TEXTURE_2D);
+ GL11.glBegin(GL11.GL_QUAD_STRIP);
+ if(wrap == Wrap.SCALE) {
+ for(Stop stop : stops) {
+ tintStack.setColor(stop.getColor());
+ float pos = stop.getPos() * width / endPos;
+ GL11.glVertex2f(x + pos, y);
+ GL11.glVertex2f(x + pos, y + height);
+ }
+ } else {
+ float lastPos = 0;
+ float offset = 0;
+ Color lastColor = stops[0].getColor();
+ outer: do{
+ for(Stop stop : stops) {
+ float pos = stop.getPos() + offset;
+ Color color = stop.getColor();
+ if(pos >= width) {
+ float t = (width - lastPos) / (pos - lastPos);
+ setColor(tintStack, lastColor, color, t);
+ break outer;
+ }
+ tintStack.setColor(color);
+ GL11.glVertex2f(x + pos, y);
+ GL11.glVertex2f(x + pos, y + height);
+ lastPos = pos;
+ lastColor = color;
+ }
+ offset += endPos;
+ }while(wrap == Wrap.REPEAT);
+ GL11.glVertex2f(x + width, y);
+ GL11.glVertex2f(x + width, y + height);
+ }
+ GL11.glEnd();
+ GL11.glEnable(GL11.GL_TEXTURE_2D);
+ }
+
+ private void drawVert(int x, int y, int width, int height) {
+ if(width <= 0 || height <= 0) {
+ return;
+ }
+ TintStack tintStack = renderer.tintStack.push(tint);
+ GL11.glDisable(GL11.GL_TEXTURE_2D);
+ GL11.glBegin(GL11.GL_QUAD_STRIP);
+ if(wrap == Wrap.SCALE) {
+ for(Stop stop : stops) {
+ tintStack.setColor(stop.getColor());
+ float pos = stop.getPos() * height / endPos;
+ GL11.glVertex2f(x , y + pos);
+ GL11.glVertex2f(x + width, y + pos);
+ }
+ } else {
+ float lastPos = 0;
+ float offset = 0;
+ Color lastColor = stops[0].getColor();
+ outer: do{
+ for(Stop stop : stops) {
+ float pos = stop.getPos() + offset;
+ Color color = stop.getColor();
+ if(pos >= height) {
+ float t = (height - lastPos) / (pos - lastPos);
+ setColor(tintStack, lastColor, color, t);
+ break outer;
+ }
+ tintStack.setColor(color);
+ GL11.glVertex2f(x , y + pos);
+ GL11.glVertex2f(x + width, y + pos);
+ lastPos = pos;
+ lastColor = color;
+ }
+ offset += endPos;
+ }while(wrap == Wrap.REPEAT);
+ GL11.glVertex2f(x , y + height);
+ GL11.glVertex2f(x + width, y + height);
+ }
+ GL11.glEnd();
+ GL11.glEnable(GL11.GL_TEXTURE_2D);
+ }
+
+ private static void setColor(TintStack tintStack, Color a, Color b, float t) {
+ tintStack.setColor(
+ mix(a.getRed(), b.getRed(), t),
+ mix(a.getGreen(), b.getGreen(), t),
+ mix(a.getBlue(), b.getBlue(), t),
+ mix(a.getAlpha(), b.getAlpha(), t));
+ }
+
+ private static float mix(int a, int b, float t) {
+ return a + (b-a) * t;
+ }
+
+}
diff --git a/twl/src/de/matthiasmann/twl/renderer/lwjgl/LWJGLAttributedStringFontCache.java b/twl/src/de/matthiasmann/twl/renderer/lwjgl/LWJGLAttributedStringFontCache.java
new file mode 100644
index 0000000..e9b0abc
--- /dev/null
+++ b/twl/src/de/matthiasmann/twl/renderer/lwjgl/LWJGLAttributedStringFontCache.java
@@ -0,0 +1,148 @@
+/*
+ * Copyright (c) 2008-2012, Matthias Mann
+ *
+ * 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 Matthias Mann 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 de.matthiasmann.twl.renderer.lwjgl;
+
+import de.matthiasmann.twl.renderer.AttributedStringFontCache;
+import de.matthiasmann.twl.renderer.lwjgl.LWJGLFont.FontState;
+import java.nio.FloatBuffer;
+import org.lwjgl.opengl.GL11;
+
+/**
+ *
+ * @author Matthias Mann
+ */
+class LWJGLAttributedStringFontCache extends VertexArray implements AttributedStringFontCache {
+
+ final LWJGLRenderer renderer;
+ final BitmapFont font;
+ int width;
+ int height;
+ private Run[] runs;
+ private int numRuns;
+
+ LWJGLAttributedStringFontCache(LWJGLRenderer renderer, BitmapFont font) {
+ this.renderer = renderer;
+ this.font = font;
+ this.runs = new Run[8];
+ }
+
+ @Override
+ public FloatBuffer allocate(int maxGlyphs) {
+ numRuns = 0;
+ return super.allocate(maxGlyphs);
+ }
+
+ Run addRun() {
+ if(runs.length == numRuns) {
+ grow();
+ }
+ Run run = runs[numRuns];
+ if(run == null) {
+ run = new Run();
+ runs[numRuns] = run;
+ }
+ numRuns++;
+ return run;
+ }
+
+ private void grow() {
+ Run[] newRuns = new Run[numRuns * 2];
+ System.arraycopy(runs, 0, newRuns, 0, numRuns);
+ runs = newRuns;
+ }
+
+ public void destroy() {
+ }
+
+ public int getWidth() {
+ return width;
+ }
+
+ public int getHeight() {
+ return height;
+ }
+
+ public void draw(int x, int y) {
+ if(font.bind()) {
+ bind();
+ GL11.glPushMatrix();
+ GL11.glTranslatef(x, y, 0f);
+ final TintStack tintStack = renderer.tintStack;
+
+ try {
+ int idx = 0;
+ for(int i=0 ; i 0) {
+ drawVertices(idx, numVertices);
+ idx += numVertices;
+ }
+
+ if(state.style != 0) {
+ drawLines(run);
+ }
+ }
+ } finally {
+ GL11.glPopMatrix();
+ unbind();
+ }
+ }
+ }
+
+ private void drawLines(Run run) {
+ final FontState state = run.state;
+
+ if((state.style & LWJGLFont.STYLE_UNDERLINE) != 0) {
+ font.drawLine(
+ run.x,
+ run.y + font.getBaseLine() + state.underlineOffset,
+ run.xend);
+ }
+ if((state.style & LWJGLFont.STYLE_LINETHROUGH) != 0) {
+ font.drawLine(
+ run.x,
+ run.y + font.getLineHeight()/2,
+ run.xend);
+ }
+ }
+
+ static class Run {
+ LWJGLFont.FontState state;
+ int numVertices;
+ int x;
+ int xend;
+ int y;
+ }
+}
diff --git a/twl/src/de/matthiasmann/twl/renderer/lwjgl/LWJGLCacheContext.java b/twl/src/de/matthiasmann/twl/renderer/lwjgl/LWJGLCacheContext.java
index 0fd7792..145e5d1 100644
--- a/twl/src/de/matthiasmann/twl/renderer/lwjgl/LWJGLCacheContext.java
+++ b/twl/src/de/matthiasmann/twl/renderer/lwjgl/LWJGLCacheContext.java
@@ -30,6 +30,7 @@
package de.matthiasmann.twl.renderer.lwjgl;
import de.matthiasmann.twl.renderer.CacheContext;
+import de.matthiasmann.twl.utils.PNGDecoder;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
@@ -38,6 +39,7 @@
import java.util.HashMap;
import org.lwjgl.BufferUtils;
import org.lwjgl.opengl.GLContext;
+import org.lwjgl.opengl.Util;
/**
*
@@ -61,9 +63,12 @@ protected LWJGLCacheContext(LWJGLRenderer renderer) {
LWJGLTexture loadTexture(URL url, LWJGLTexture.Format fmt, LWJGLTexture.Filter filter) throws IOException {
String urlString = url.toString();
+ Util.checkGLError();
LWJGLTexture texture = textures.get(urlString);
+ Util.checkGLError();
if(texture == null) {
texture = createTexture(url, fmt, filter, null);
+ Util.checkGLError();
textures.put(urlString, texture);
}
return texture;
@@ -73,10 +78,15 @@ LWJGLTexture createTexture(URL textureUrl, LWJGLTexture.Format fmt, LWJGLTexture
if(!valid) {
throw new IllegalStateException("CacheContext already destroyed");
}
+ Util.checkGLError();
InputStream is = textureUrl.openStream();
+ Util.checkGLError();
try {
+ Util.checkGLError();
PNGDecoder dec = new PNGDecoder(is);
- fmt = dec.decideTextureFormat(fmt);
+ Util.checkGLError();
+ fmt = decideTextureFormat(dec, fmt);
+ Util.checkGLError();
int width = dec.getWidth();
int height = dec.getHeight();
int maxTextureSize = renderer.maxTextureSize;
@@ -84,24 +94,32 @@ LWJGLTexture createTexture(URL textureUrl, LWJGLTexture.Format fmt, LWJGLTexture
if(width > maxTextureSize || height > maxTextureSize) {
throw new IOException("Texture size too large. Maximum supported texture by this system is " + maxTextureSize);
}
-
+ Util.checkGLError();
if(GLContext.getCapabilities().GL_EXT_abgr) {
+ Util.checkGLError();
if(fmt == LWJGLTexture.Format.RGBA) {
+ Util.checkGLError();
fmt = LWJGLTexture.Format.ABGR;
}
} else if(fmt == LWJGLTexture.Format.ABGR) {
+ Util.checkGLError();
fmt = LWJGLTexture.Format.RGBA;
}
-
+ Util.checkGLError();
int stride = width * fmt.getPixelSize();
+ Util.checkGLError();
ByteBuffer buf = BufferUtils.createByteBuffer(stride * height);
- dec.decode(buf, stride, fmt);
+ Util.checkGLError();
+ dec.decode(buf, stride, fmt.getPngFormat());
+ Util.checkGLError();
buf.flip();
if(tpp != null) {
+ Util.checkGLError();
tpp.process(buf, stride, width, height, fmt);
+ Util.checkGLError();
}
-
+ Util.checkGLError();
LWJGLTexture texture = new LWJGLTexture(renderer, width, height, buf, fmt, filter);
allTextures.add(texture);
return texture;
@@ -145,4 +163,47 @@ public void destroy() {
}
}
+ private static LWJGLTexture.Format decideTextureFormat(PNGDecoder decoder, LWJGLTexture.Format fmt) {
+ if(fmt == LWJGLTexture.Format.COLOR) {
+ fmt = autoColorFormat(decoder);
+ }
+
+ PNGDecoder.Format pngFormat = decoder.decideTextureFormat(fmt.getPngFormat());
+ if(fmt.pngFormat == pngFormat) {
+ return fmt;
+ }
+
+ switch(pngFormat) {
+ case ALPHA:
+ return LWJGLTexture.Format.ALPHA;
+ case LUMINANCE:
+ return LWJGLTexture.Format.LUMINANCE;
+ case LUMINANCE_ALPHA:
+ return LWJGLTexture.Format.LUMINANCE_ALPHA;
+ case RGB:
+ return LWJGLTexture.Format.RGB;
+ case RGBA:
+ return LWJGLTexture.Format.RGBA;
+ case BGRA:
+ return LWJGLTexture.Format.BGRA;
+ case ABGR:
+ return LWJGLTexture.Format.ABGR;
+ default:
+ throw new UnsupportedOperationException("PNGFormat not handled: " + pngFormat);
+ }
+ }
+
+ private static LWJGLTexture.Format autoColorFormat(PNGDecoder decoder) {
+ if(decoder.hasAlpha()) {
+ if(decoder.isRGB()) {
+ return LWJGLTexture.Format.ABGR;
+ } else {
+ return LWJGLTexture.Format.LUMINANCE_ALPHA;
+ }
+ } else if(decoder.isRGB()) {
+ return LWJGLTexture.Format.ABGR;
+ } else {
+ return LWJGLTexture.Format.LUMINANCE;
+ }
+ }
}
diff --git a/twl/src/de/matthiasmann/twl/renderer/lwjgl/LWJGLDynamicImage.java b/twl/src/de/matthiasmann/twl/renderer/lwjgl/LWJGLDynamicImage.java
index c3c56ea..90f3e04 100644
--- a/twl/src/de/matthiasmann/twl/renderer/lwjgl/LWJGLDynamicImage.java
+++ b/twl/src/de/matthiasmann/twl/renderer/lwjgl/LWJGLDynamicImage.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2008-2010, Matthias Mann
+ * Copyright (c) 2008-2012, Matthias Mann
*
* All rights reserved.
*
@@ -48,10 +48,11 @@ public class LWJGLDynamicImage extends TextureAreaBase implements DynamicImage {
private final Color tintColor;
private int id;
- public LWJGLDynamicImage(LWJGLRenderer renderer, int target, int id, int width, int height, Color tintColor) {
+ public LWJGLDynamicImage(LWJGLRenderer renderer, int target, int id,
+ int width, int height, int texWidth, int texHeight, Color tintColor) {
super(0, 0, width, height,
- (target == GL11.GL_TEXTURE_2D) ? width : 1f,
- (target == GL11.GL_TEXTURE_2D) ? height : 1f);
+ (target == GL11.GL_TEXTURE_2D) ? texWidth : 1f,
+ (target == GL11.GL_TEXTURE_2D) ? texHeight : 1f);
this.renderer = renderer;
this.tintColor = tintColor;
@@ -59,18 +60,34 @@ public LWJGLDynamicImage(LWJGLRenderer renderer, int target, int id, int width,
this.id = id;
}
+ LWJGLDynamicImage(LWJGLDynamicImage src, Color tintColor) {
+ super(src);
+ this.renderer = src.renderer;
+ this.tintColor = tintColor;
+ this.target = src.target;
+ this.id = src.id;
+ }
+
public void destroy() {
if(id != 0) {
- renderer.glDeleteTexture(id);
+ GL11.glDeleteTextures(id);
renderer.dynamicImages.remove(this);
}
}
public void update(ByteBuffer data, Format format) {
- update(0, 0, width, height, data, format);
+ update(0, 0, width, height, data, width*4, format);
+ }
+
+ public void update(ByteBuffer data, int stride, Format format) {
+ update(0, 0, width, height, data, stride, format);
}
public void update(int xoffset, int yoffset, int width, int height, ByteBuffer data, Format format) {
+ update(xoffset, yoffset, width, height, data, width*4, format);
+ }
+
+ public void update(int xoffset, int yoffset, int width, int height, ByteBuffer data, int stride, Format format) {
if(xoffset < 0 || yoffset < 0 || getWidth() <= 0 || getHeight() <= 0) {
throw new IllegalArgumentException("Negative offsets or size <= 0");
}
@@ -86,12 +103,20 @@ public void update(int xoffset, int yoffset, int width, int height, ByteBuffer d
if(format == null) {
throw new NullPointerException("format");
}
- if(data.remaining() < width*height*4) {
+ if(stride < 0 || (stride & 3) != 0) {
+ throw new IllegalArgumentException("stride");
+ }
+ if(stride < width*4) {
+ throw new IllegalArgumentException("stride too short for width");
+ }
+ if(data.remaining() < stride*(height-1)+width*4) {
throw new IllegalArgumentException("Not enough data remaining in the buffer");
}
int glFormat = (format == Format.RGBA) ? GL11.GL_RGBA : GL12.GL_BGRA;
bind();
+ GL11.glPixelStorei(GL11.GL_UNPACK_ROW_LENGTH, stride/4);
GL11.glTexSubImage2D(target, 0, xoffset, yoffset, width, height, glFormat, GL11.GL_UNSIGNED_BYTE, data);
+ GL11.glPixelStorei(GL11.GL_UNPACK_ROW_LENGTH, 0);
}
public Image createTintedVersion(Color color) {
@@ -102,7 +127,7 @@ public Image createTintedVersion(Color color) {
if(newTintColor.equals(tintColor)) {
return this;
}
- return new LWJGLDynamicImage(renderer, target, id, getWidth(), getHeight(), newTintColor);
+ return new LWJGLDynamicImage(this, newTintColor);
}
public void draw(AnimationState as, int x, int y) {
diff --git a/twl/src/de/matthiasmann/twl/renderer/lwjgl/LWJGLFont.java b/twl/src/de/matthiasmann/twl/renderer/lwjgl/LWJGLFont.java
index 623dc7f..8ff23a5 100644
--- a/twl/src/de/matthiasmann/twl/renderer/lwjgl/LWJGLFont.java
+++ b/twl/src/de/matthiasmann/twl/renderer/lwjgl/LWJGLFont.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2008-2010, Matthias Mann
+ * Copyright (c) 2008-2012, Matthias Mann
*
* All rights reserved.
*
@@ -32,20 +32,21 @@
import de.matthiasmann.twl.Color;
import de.matthiasmann.twl.HAlignment;
import de.matthiasmann.twl.renderer.AnimationState;
+import de.matthiasmann.twl.renderer.AttributedString;
+import de.matthiasmann.twl.renderer.AttributedStringFontCache;
import de.matthiasmann.twl.renderer.Font;
+import de.matthiasmann.twl.renderer.Font2;
import de.matthiasmann.twl.renderer.FontCache;
import de.matthiasmann.twl.renderer.FontParameter;
-import de.matthiasmann.twl.utils.StateExpression;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.HashMap;
-import java.util.Map;
+import de.matthiasmann.twl.utils.StateSelect;
+import de.matthiasmann.twl.utils.TextUtil;
+import java.nio.FloatBuffer;
/**
*
* @author Matthias Mann
*/
-public class LWJGLFont implements Font {
+public class LWJGLFont implements Font, Font2 {
static final int STYLE_UNDERLINE = 1;
static final int STYLE_LINETHROUGH = 2;
@@ -53,74 +54,75 @@ public class LWJGLFont implements Font {
private final LWJGLRenderer renderer;
private final BitmapFont font;
private final FontState[] fontStates;
+ private final StateSelect stateSelect;
private int[] multiLineInfo;
- LWJGLFont(LWJGLRenderer renderer, BitmapFont font, Map params, Collection condParams) {
+ public LWJGLFont clone()
+ {
+ return new LWJGLFont(this);
+ }
+
+ private LWJGLFont(LWJGLFont oldFont)
+ {
+ renderer = oldFont.renderer;
+ font = oldFont.font;
+ stateSelect = oldFont.stateSelect;
+ fontStates = new FontState[oldFont.fontStates.length];
+ for (int i = 0; i < fontStates.length; i++) {
+ FontState oldState = oldFont.fontStates[i];
+ fontStates[i] = new FontState(oldState);
+ }
+ }
+
+
+ LWJGLFont(LWJGLRenderer renderer, BitmapFont font, StateSelect select, FontParameter ... parameterList) {
this.renderer = renderer;
this.font = font;
-
- ArrayList states = new ArrayList();
- for(FontParameter p : condParams) {
- HashMap effective = new HashMap(params);
- effective.putAll(p.getParams());
- states.add(createFontState(p.getCondition(), effective));
+ this.stateSelect = select;
+ this.fontStates = new FontState[parameterList.length];
+
+ for(int i=0 ; i params) {
- String colorStr = params.get("color");
- if(colorStr == null) {
- throw new IllegalArgumentException("color needs to be defined");
- }
- int offsetX = parseInt(params.get("offsetX"), 0);
- int offsetY = parseInt(params.get("offsetY"), 0);
- int style = 0;
- int underlineOffset = parseInt(params.get("underlineOffset"), 0);
- Color color = Color.parserColor(colorStr);
- if(color == null) {
- throw new IllegalArgumentException("unknown color name: " + colorStr);
- }
- if(parseBoolean(params.get("underline"))) {
- style |= STYLE_UNDERLINE;
- }
- if(parseBoolean(params.get("linethrough"))) {
- style |= STYLE_LINETHROUGH;
- }
- FontState p = new FontState(cond, color, offsetX, offsetY, style, underlineOffset);
- return p;
+ public FontState evalFontState(AnimationState as) {
+ return fontStates[stateSelect.evaluate(as)];
}
- private static int parseInt(String valueStr, int defaultValue) {
- if(valueStr == null) {
- return defaultValue;
+ private int[] getMultiLineInfo(int numLines) {
+ if(multiLineInfo == null || multiLineInfo.length < numLines) {
+ multiLineInfo = new int[numLines];
}
- return Integer.parseInt(valueStr);
+ return multiLineInfo;
}
- private static boolean parseBoolean(String valueStr) {
- if(valueStr == null) {
- return false;
- }
- return Boolean.parseBoolean(valueStr);
+ public void destroy() {
+ font.destroy();
}
- FontState evalFontState(AnimationState as) {
- int i = 0;
- for(int n=fontStates.length-1 ; i 0) {
+ g.draw(x, y);
+ }
+ x += g.xadvance;
+ }
+ }
+ drawLine(fontState, x, y, x - runStart);
+ x -= fontState.offsetX;
+ y -= fontState.offsetY;
+ if(multiLine && start < end && attributedString.charAt(start) == '\n') {
+ attributedString.setPosition(++start);
+ x = startX;
+ y += font.getLineHeight();
+ lastGlyph = null;
+ }
+ }while(start < end);
+ } finally {
+ font.cleanup();
+ }
+ return x - startX;
}
- public int getEM() {
- return font.getEM();
+ public AttributedStringFontCache cacheText(AttributedStringFontCache prevCache, AttributedString attributedString) {
+ return cacheText(prevCache, attributedString, 0, attributedString.length(), false);
}
- public int getEX() {
- return font.getEX();
+ public AttributedStringFontCache cacheText(AttributedStringFontCache prevCache, AttributedString attributedString, int start, int end) {
+ return cacheText(prevCache, attributedString, start, end, false);
}
- public void destroy() {
- font.destroy();
+ public AttributedStringFontCache cacheMultiLineText(AttributedStringFontCache prevCache, AttributedString attributedString) {
+ return cacheText(prevCache, attributedString, 0, attributedString.length(), true);
+ }
+
+ public AttributedStringFontCache cacheMultiLineText(AttributedStringFontCache prevCache, AttributedString attributedString, int start, int end) {
+ return cacheText(prevCache, attributedString, start, end, true);
}
- static class FontState {
- final StateExpression condition;
- final Color color;
- final int offsetX;
- final int offsetY;
- final int style;
- final int underlineOffset;
-
- public FontState(StateExpression condition, Color color, int offsetX, int offsetY, int style, int underlineOffset) {
- this.condition = condition;
- this.color = color;
- this.offsetX = offsetX;
- this.offsetY = offsetY;
- this.style = style;
- this.underlineOffset = underlineOffset;
+ private AttributedStringFontCache cacheText(AttributedStringFontCache prevCache, AttributedString attributedString, int start, int end, boolean multiLine) {
+ if(end <= start) {
+ return null;
+ }
+ LWJGLAttributedStringFontCache cache = (LWJGLAttributedStringFontCache)prevCache;
+ if(cache == null) {
+ cache = new LWJGLAttributedStringFontCache(renderer, font);
}
+ FloatBuffer va = cache.allocate(end - start);
+ attributedString.setPosition(start);
+ BitmapFont.Glyph lastGlyph = null;
+ int x = 0;
+ int y = 0;
+ int width = 0;
+ do{
+ final FontState fontState = evalFontState(attributedString);
+
+ x += fontState.offsetX;
+ y += fontState.offsetY;
+ int runLength = 0;
+ int xStart = x;
+
+ int nextStop = Math.min(end, attributedString.advance());
+ while(nextStop < end && fontState == evalFontState(attributedString)) {
+ nextStop = Math.min(end, attributedString.advance());
+ }
+
+ if(multiLine) {
+ nextStop = TextUtil.indexOf(attributedString, '\n', start, nextStop);
+ }
+
+ while(start < nextStop) {
+ char ch = attributedString.charAt(start++);
+ BitmapFont.Glyph g = font.getGlyph(ch);
+ if(g != null) {
+ if(lastGlyph != null) {
+ x += lastGlyph.getKerning(ch);
+ }
+ lastGlyph = g;
+ if(g.width > 0 && g.height > 0) {
+ g.draw(va, x, y);
+ runLength++;
+ }
+ x += g.xadvance;
+ }
+ }
+
+ x -= fontState.offsetX;
+ y -= fontState.offsetY;
+
+ if(runLength > 0 || fontState.style != 0) {
+ LWJGLAttributedStringFontCache.Run run = cache.addRun();
+ run.state = fontState;
+ run.numVertices = runLength * 4;
+ run.x = xStart;
+ run.xend = x;
+ run.y = y;
+ }
+
+ if(multiLine && start < end && attributedString.charAt(start) == '\n') {
+ attributedString.setPosition(++start);
+ width = Math.max(width, x);
+ x = 0;
+ y += font.getLineHeight();
+ lastGlyph = null;
+ }
+ }while(start < end);
+
+ if(x > 0) {
+ width = Math.max(width, x);
+ y += font.getLineHeight();
+ }
+
+ cache.width = width;
+ cache.height = y;
+ return cache;
}
+
+ public static class FontState {
+ Color color;
+ int offsetX;
+ int offsetY;
+ int style;
+ int underlineOffset;
+
+ FontState(FontParameter fontParam) {
+ int lineStyle = 0;
+ if (fontParam.get(FontParameter.UNDERLINE)) {
+ lineStyle |= STYLE_UNDERLINE;
+ }
+ if (fontParam.get(FontParameter.LINETHROUGH)) {
+ lineStyle |= STYLE_LINETHROUGH;
+ }
+
+ this.color = fontParam.get(FontParameter.COLOR);
+ this.offsetX = fontParam.get(LWJGLRenderer.FONTPARAM_OFFSET_X);
+ this.offsetY = fontParam.get(LWJGLRenderer.FONTPARAM_OFFSET_Y);
+ this.style = lineStyle;
+ this.underlineOffset = fontParam
+ .get(LWJGLRenderer.FONTPARAM_UNDERLINE_OFFSET);
+ }
+
+ public FontState(FontState oldState) {
+ this.color = oldState.color;
+ this.offsetX = oldState.offsetX;
+ this.offsetY = oldState.offsetY;
+ this.style = oldState.style;
+ this.underlineOffset = oldState.underlineOffset;
+ }
+
+ public FontState(Color color, int offsetX, int offsetY, int style,
+ int underlineOffset) {
+ this.color = color;
+ this.offsetX = offsetX;
+ this.offsetY = offsetY;
+ this.style = style;
+ this.underlineOffset = underlineOffset;
+ }
+
+ public Color getColor() {
+ return color;
+ }
+
+ public boolean getLineThrough() {
+ return (style & 2) == 2;
+ }
+
+ public int getOffsetX() {
+ return offsetX;
+ }
+
+ public int getOffsetY() {
+ return offsetY;
+ }
+
+ public boolean getUnderline() {
+ return (style & 1) == 1;
+ }
+
+ public int getUnderlineOffset() {
+ return underlineOffset;
+ }
+
+ public void setColor(Color col) {
+ color = col;
+ }
+
+ public void setUnderlineOffset(int i) {
+ underlineOffset = i;
+ }
+
+ public void setUnderline(boolean val) {
+ if (getUnderline() != val) {
+ style ^= 1;
+ }
+ }
+
+ public void setOffsetY(int i) {
+ offsetY = i;
+ }
+
+ public void setOffsetX(int i) {
+ offsetX = i;
+ }
+
+ public void setLineThrough(boolean val) {
+ if (getLineThrough() != val) {
+ style ^= 2;
+ }
+ }
+
+ }
}
diff --git a/twl/src/de/matthiasmann/twl/renderer/lwjgl/LWJGLRenderer.java b/twl/src/de/matthiasmann/twl/renderer/lwjgl/LWJGLRenderer.java
index 49fdeb9..bdca1ae 100644
--- a/twl/src/de/matthiasmann/twl/renderer/lwjgl/LWJGLRenderer.java
+++ b/twl/src/de/matthiasmann/twl/renderer/lwjgl/LWJGLRenderer.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2008-2010, Matthias Mann
+ * Copyright (c) 2008-2012, Matthias Mann
*
* All rights reserved.
*
@@ -37,19 +37,23 @@
import de.matthiasmann.twl.renderer.CacheContext;
import de.matthiasmann.twl.renderer.DynamicImage;
import de.matthiasmann.twl.renderer.FontParameter;
+import de.matthiasmann.twl.renderer.Gradient;
+import de.matthiasmann.twl.renderer.Image;
import de.matthiasmann.twl.renderer.MouseCursor;
import de.matthiasmann.twl.renderer.Font;
+import de.matthiasmann.twl.renderer.FontMapper;
import de.matthiasmann.twl.renderer.LineRenderer;
+import de.matthiasmann.twl.renderer.OffscreenRenderer;
import de.matthiasmann.twl.renderer.Renderer;
import de.matthiasmann.twl.renderer.Texture;
+import de.matthiasmann.twl.utils.ClipStack;
+import de.matthiasmann.twl.utils.StateSelect;
import java.io.IOException;
import java.net.URL;
import java.nio.ByteBuffer;
import java.nio.IntBuffer;
import java.util.ArrayList;
-import java.util.Collection;
import java.util.Locale;
-import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.lwjgl.BufferUtils;
@@ -61,6 +65,7 @@
import org.lwjgl.opengl.EXTTextureRectangle;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GLContext;
+import org.lwjgl.opengl.Util;
/**
* A renderer using only GL11 features.
@@ -77,11 +82,15 @@ public class LWJGLRenderer implements Renderer, LineRenderer {
public static final StateKey STATE_MIDDLE_MOUSE_BUTTON = StateKey.get("middleMouseButton");
public static final StateKey STATE_RIGHT_MOUSE_BUTTON = StateKey.get("rightMouseButton");
+ public static final FontParameter.Parameter FONTPARAM_OFFSET_X = FontParameter.newParameter("offsetX", 0);
+ public static final FontParameter.Parameter FONTPARAM_OFFSET_Y = FontParameter.newParameter("offsetY", 0);
+ public static final FontParameter.Parameter FONTPARAM_UNDERLINE_OFFSET = FontParameter.newParameter("underlineOffset", 0);
+
private final IntBuffer ib16;
final int maxTextureSize;
private int viewportX;
- private int viewportY;
+ private int viewportBottom;
private int width;
private int height;
private boolean hasScissor;
@@ -93,19 +102,27 @@ public class LWJGLRenderer implements Renderer, LineRenderer {
private int mouseX;
private int mouseY;
private LWJGLCacheContext cacheContext;
+ private FontMapper fontMapper;
final SWCursorAnimState swCursorAnimState;
final ArrayList textureAreas;
+ final ArrayList rotatedTextureAreas;
final ArrayList dynamicImages;
- TintStack tintStack;
-
+
+ protected TintStack tintStack;
+ protected final ClipStack clipStack;
+ protected final Rect clipRectTemp;
+
@SuppressWarnings("OverridableMethodCallInConstructor")
public LWJGLRenderer() throws LWJGLException {
this.ib16 = BufferUtils.createIntBuffer(16);
this.textureAreas = new ArrayList();
+ this.rotatedTextureAreas = new ArrayList();
this.dynamicImages = new ArrayList();
this.tintStateRoot = new TintStack();
this.tintStack = tintStateRoot;
+ this.clipStack = new ClipStack();
+ this.clipRectTemp = new Rect();
syncViewportSize();
GL11.glGetInteger(GL11.GL_MAX_TEXTURE_SIZE, ib16);
@@ -181,8 +198,12 @@ public void setActiveCacheContext(CacheContext cc) throws IllegalStateException
for(TextureArea ta : textureAreas) {
ta.destroyRepeatCache();
}
+ for(TextureAreaRotated tar : rotatedTextureAreas) {
+ tar.destroyRepeatCache();
+ }
} finally {
textureAreas.clear();
+ rotatedTextureAreas.clear();
}
}
@@ -203,9 +224,26 @@ public void syncViewportSize() {
ib16.clear();
GL11.glGetInteger(GL11.GL_VIEWPORT, ib16);
viewportX = ib16.get(0);
- viewportY = ib16.get(1);
width = ib16.get(2);
height = ib16.get(3);
+ viewportBottom = ib16.get(1) + height;
+ }
+
+ /**
+ * Sets the viewport size & position.
+ *
This method is preferred over {@link #syncViewportSize() } as it avoids
+ * calling {@link GL11#glGetInteger(int, java.nio.IntBuffer) }.
+ *
+ * @param x the X position (GL_VIEWPORT index 0)
+ * @param y the Y position (GL_VIEWPORT index 1)
+ * @param width the width (GL_VIEWPORT index 2)
+ * @param height the height (GL_VIEWPORT index 3)
+ */
+ public void setViewport(int x, int y, int width, int height) {
+ this.viewportX = x;
+ this.viewportBottom = y + height;
+ this.width = width;
+ this.height = height;
}
public long getTimeMillis() {
@@ -216,18 +254,8 @@ public long getTimeMillis() {
}
return time;
}
-
- /**
- * Setup GL to start rendering the GUI. It assumes default GL state.
- */
- public boolean startRenderering() {
- if(width <= 0 || height <= 0) {
- return false;
- }
-
- hasScissor = false;
- tintStack = tintStateRoot;
-
+
+ protected void setupGLState() {
GL11.glPushAttrib(GL11.GL_ENABLE_BIT|GL11.GL_TRANSFORM_BIT|GL11.GL_HINT_BIT|
GL11.GL_COLOR_BUFFER_BIT|GL11.GL_SCISSOR_BIT|GL11.GL_LINE_BIT|GL11.GL_TEXTURE_BIT);
GL11.glMatrixMode(GL11.GL_PROJECTION);
@@ -242,23 +270,56 @@ public boolean startRenderering() {
GL11.glEnable(GL11.GL_LINE_SMOOTH);
GL11.glDisable(GL11.GL_DEPTH_TEST);
GL11.glDisable(GL11.GL_LIGHTING);
+ GL11.glDisable(GL11.GL_SCISSOR_TEST);
GL11.glBlendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA);
GL11.glHint(GL11.GL_LINE_SMOOTH_HINT, GL11.GL_NICEST);
- RenderScale.doscale();
+ }
+
+ protected void revertGLState() {
+ GL11.glPopMatrix();
+ GL11.glMatrixMode(GL11.GL_PROJECTION);
+ GL11.glPopMatrix();
+ GL11.glPopAttrib();
+ }
+
+ /**
+ * Setup GL to start rendering the GUI. It assumes default GL state.
+ */
+ public boolean startRendering() {
+ if(width <= 0 || height <= 0) {
+ return false;
+ }
+ prepareForRendering();
+ setupGLState();
+ RenderScale.doscale();
return true;
}
public void endRendering() {
- if(swCursor != null) {
- tintStack = tintStateRoot;
- swCursor.render(mouseX, mouseY);
- }
+ renderSWCursor();
RenderScale.descale();
- GL11.glPopMatrix();
- GL11.glMatrixMode(GL11.GL_PROJECTION);
- GL11.glPopMatrix();
- GL11.glPopAttrib();
+ revertGLState();
+ }
+
+ /**
+ * Call to revert the GL state to the state before calling
+ * {@link #startRendering()}.
+ * @see #resumeRendering()
+ */
+ public void pauseRendering() {
+ RenderScale.descale();
+ revertGLState();
+ }
+
+ /**
+ * Resume rendering after a call to {@link #pauseRendering()}.
+ */
+ public void resumeRendering() {
+ hasScissor = false;
+ setupGLState();
+ RenderScale.doscale();
+ setClipRect();
}
public int getHeight() {
@@ -268,15 +329,42 @@ public int getHeight() {
public int getWidth() {
return width;
}
+
+ /**
+ * Retrieves the X position of the OpenGL viewport (index 0 of GL_VIEWPORT)
+ * @return the X position of the OpenGL viewport
+ */
+ public int getViewportX() {
+ return viewportX;
+ }
+
+ /**
+ * Retrieves the Y position of the OpenGL viewport (index 1 of GL_VIEWPORT)
+ * @return the Y position of the OpenGL viewport
+ */
+ public int getViewportY() {
+ return viewportBottom - height;
+ }
- public Font loadFont(URL baseUrl, Map parameter, Collection conditionalParameter) throws IOException {
- String fileName = parameter.get("filename");
- if(fileName == null) {
- throw new IllegalArgumentException("filename parameter required");
+ public Font loadFont(URL url, StateSelect select, FontParameter ... parameterList) throws IOException {
+ Util.checkGLError();
+ if(url == null) {
+ throw new NullPointerException("url");
+ }
+ if(select == null) {
+ throw new NullPointerException("select");
}
- URL url = new URL(baseUrl, fileName);
+ if(parameterList == null) {
+ throw new NullPointerException("parameterList");
+ }
+
+ if(select.getNumExpressions() + 1 != parameterList.length) {
+ throw new IllegalArgumentException("select.getNumExpressions() + 1 != parameterList.length");
+ }
+ Util.checkGLError();
BitmapFont bmFont = activeCacheContext().loadBitmapFont(url);
- return new LWJGLFont(this, bmFont, parameter, conditionalParameter);
+ Util.checkGLError();
+ return new LWJGLFont(this, bmFont, select, parameterList);
}
public Texture loadTexture(URL url, String formatStr, String filterStr) throws IOException {
@@ -303,6 +391,25 @@ public LineRenderer getLineRenderer() {
return this;
}
+ public OffscreenRenderer getOffscreenRenderer() {
+ return null;
+ }
+
+ public FontMapper getFontMapper() {
+ return fontMapper;
+ }
+
+ /**
+ * Installs a font mapper. It is the responsibility of the font mapper to
+ * manage the OpenGL state correctly so that normal rendering by LWJGLRenderer
+ * is not disturbed.
+ *
+ * @param fontMapper the font mapper object - can be null.
+ */
+ public void setFontMapper(FontMapper fontMapper) {
+ this.fontMapper = fontMapper;
+ }
+
public DynamicImage createDynamicImage(int width, int height) {
if(width <= 0) {
throw new IllegalArgumentException("width");
@@ -311,73 +418,88 @@ public DynamicImage createDynamicImage(int width, int height) {
throw new IllegalArgumentException("height");
}
if(width > maxTextureSize || height > maxTextureSize) {
+ getLogger().log(Level.WARNING, "requested size {0} x {1} exceeds maximum texture size {3}",
+ new Object[]{ width, height, maxTextureSize });
return null;
}
+ int texWidth = width;
+ int texHeight = height;
+
ContextCapabilities caps = GLContext.getCapabilities();
boolean useTextureRectangle = caps.GL_EXT_texture_rectangle || caps.GL_ARB_texture_rectangle;
if(!useTextureRectangle && !caps.GL_ARB_texture_non_power_of_two) {
- if((width & (width-1)) != 0 || (height & (height-1)) != 0) {
- return null;
- }
+ texWidth = nextPowerOf2(width);
+ texHeight = nextPowerOf2(height);
}
// ARB and EXT versions use the same enum !
int proxyTarget = useTextureRectangle ?
EXTTextureRectangle.GL_PROXY_TEXTURE_RECTANGLE_EXT : GL11.GL_PROXY_TEXTURE_2D;
- GL11.glTexImage2D(proxyTarget, 0, GL11.GL_RGBA, width, height, 0, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, (ByteBuffer)null);
+ GL11.glTexImage2D(proxyTarget, 0, GL11.GL_RGBA, texWidth, texHeight, 0, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, (ByteBuffer)null);
ib16.clear();
GL11.glGetTexLevelParameter(proxyTarget, 0, GL11.GL_TEXTURE_WIDTH, ib16);
- if(ib16.get(0) != width) {
+ if(ib16.get(0) != texWidth) {
+ getLogger().log(Level.WARNING, "requested size {0} x {1} failed proxy texture test",
+ new Object[]{ texWidth, texHeight });
return null;
}
// ARB and EXT versions use the same enum !
int target = useTextureRectangle ?
EXTTextureRectangle.GL_TEXTURE_RECTANGLE_EXT : GL11.GL_TEXTURE_2D;
- int id = glGenTexture();
+ int id = GL11.glGenTextures();
GL11.glBindTexture(target, id);
- GL11.glTexImage2D(target, 0, GL11.GL_RGBA, width, height, 0, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, (ByteBuffer)null);
+ GL11.glTexImage2D(target, 0, GL11.GL_RGBA, texWidth, texHeight, 0, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, (ByteBuffer)null);
GL11.glTexParameteri(target, GL11.GL_TEXTURE_MAG_FILTER, GL11.GL_NEAREST);
GL11.glTexParameteri(target, GL11.GL_TEXTURE_MIN_FILTER, GL11.GL_NEAREST);
- LWJGLDynamicImage image = new LWJGLDynamicImage(this, target, id, width, height, Color.WHITE);
+ LWJGLDynamicImage image = new LWJGLDynamicImage(this, target, id, width, height, texWidth, texHeight, Color.WHITE);
dynamicImages.add(image);
return image;
}
- public void setClipRect(Rect rect) {
- if(rect == null) {
- GL11.glDisable(GL11.GL_SCISSOR_TEST);
- hasScissor = false;
- } else {
- GL11.glScissor(viewportX + rect.getX()*RenderScale.scale, viewportY + getHeight() - rect.getBottom()*RenderScale.scale, rect.getWidth()*RenderScale.scale, rect.getHeight()*RenderScale.scale);
- if(!hasScissor) {
- GL11.glEnable(GL11.GL_SCISSOR_TEST);
- hasScissor = true;
- }
- }
+ public Image createGradient(Gradient gradient) {
+ return new GradientImage(this, gradient);
+ }
+
+ public void clipEnter(int x, int y, int w, int h) {
+ clipStack.push(x, y, w, h);
+ setClipRect();
+ }
+
+ public void clipEnter(Rect rect) {
+ clipStack.push(rect);
+ setClipRect();
+ }
+
+ public void clipLeave() {
+ clipStack.pop();
+ setClipRect();
+ }
+
+ public boolean clipIsEmpty() {
+ return clipStack.isClipEmpty();
}
public void setCursor(MouseCursor cursor) {
try {
- if(Mouse.isInsideWindow()) {
- swCursor = null;
+ swCursor = null;
+ if(isMouseInsideWindow()) {
if(cursor instanceof LWJGLCursor) {
- Mouse.setNativeCursor(((LWJGLCursor)cursor).cursor);
+ setNativeCursor(((LWJGLCursor)cursor).cursor);
} else if(cursor instanceof SWCursor) {
- Mouse.setNativeCursor(emptyCursor);
+ setNativeCursor(emptyCursor);
swCursor = (SWCursor)cursor;
} else {
- Mouse.setNativeCursor(null);
+ setNativeCursor(null);
}
}
} catch(LWJGLException ex) {
- Logger.getLogger(LWJGLRenderer.class.getName()).log(Level.WARNING,
- "Could not set native cursor", ex);
+ getLogger().log(Level.WARNING, "Could not set native cursor", ex);
}
}
@@ -391,17 +513,22 @@ public void setMouseButton(int button, boolean state) {
}
public LWJGLTexture load(URL textureUrl, LWJGLTexture.Format fmt, LWJGLTexture.Filter filter) throws IOException {
- return load(textureUrl, fmt, filter, null);
+ Util.checkGLError();
+ return load(textureUrl, fmt, filter, null);
}
public LWJGLTexture load(URL textureUrl, LWJGLTexture.Format fmt, LWJGLTexture.Filter filter, TexturePostProcessing tpp) throws IOException {
if(textureUrl == null) {
throw new NullPointerException("textureUrl");
}
+ Util.checkGLError();
LWJGLCacheContext cc = activeCacheContext();
+ Util.checkGLError();
if(tpp != null) {
+ Util.checkGLError();
return cc.createTexture(textureUrl, fmt, filter, tpp);
} else {
+ Util.checkGLError();
return cc.loadTexture(textureUrl, fmt, filter);
}
}
@@ -413,6 +540,15 @@ public void pushGlobalTintColor(float r, float g, float b, float a) {
public void popGlobalTintColor() {
tintStack = tintStack.pop();
}
+
+ /**
+ * Pushes a white entry on the tint stack which ignores the previous
+ * tint color. It must be removed by calling {@link #popGlobalTintColor()}.
+ *
This is useful when rendering to texture
+ */
+ public void pushGlobalTintColorReset() {
+ tintStack = tintStack.pushReset();
+ }
/**
* Calls GL11.glColor4f() with the specified color multiplied by the current global tint color.
@@ -473,34 +609,97 @@ private static void drawLineAsQuad(float x0, float y0, float x1, float y1, float
GL11.glVertex2f(x1 + dx + dy, y1 + dy - dx);
}
+ protected void prepareForRendering() {
+ hasScissor = false;
+ tintStack = tintStateRoot;
+ clipStack.clearStack();
+ }
+
+ protected void renderSWCursor() {
+ if(swCursor != null) {
+ tintStack = tintStateRoot;
+ swCursor.render(mouseX, mouseY);
+ }
+ }
+
+ protected void setNativeCursor(Cursor cursor) throws LWJGLException {
+ Mouse.setNativeCursor(cursor);
+ }
+
+ protected boolean isMouseInsideWindow() {
+ return Mouse.isInsideWindow();
+ }
+
protected void getTintedColor(Color color, float[] result) {
- result[0] = tintStack.r*(color.getR()&255);
- result[1] = tintStack.g*(color.getG()&255);
- result[2] = tintStack.b*(color.getB()&255);
- result[3] = tintStack.a*(color.getA()&255);
+ result[0] = tintStack.r*color.getRed();
+ result[1] = tintStack.g*color.getGreen();
+ result[2] = tintStack.b*color.getBlue();
+ result[3] = tintStack.a*color.getAlpha();
+ }
+
+ /**
+ * Computes the tinted color from the given color.
+ * @param color the input color in RGBA order, value range is 0.0 (black) to 255.0 (white).
+ * @param result the tinted color in RGBA order, can be the same array as color.
+ */
+ protected void getTintedColor(float[] color, float[] result) {
+ result[0] = tintStack.r*color[0];
+ result[1] = tintStack.g*color[1];
+ result[2] = tintStack.b*color[2];
+ result[3] = tintStack.a*color[3];
}
- Logger getLogger() {
- return Logger.getLogger(LWJGLRenderer.class.getName());
+ public void setClipRect() {
+ final Rect rect = clipRectTemp;
+ if(clipStack.getClipRect(rect)) {
+ GL11.glScissor(viewportX + rect.getX()*RenderScale.scale, viewportBottom - rect.getBottom()*RenderScale.scale, rect.getWidth()*RenderScale.scale, rect.getHeight()*RenderScale.scale);
+ if(!hasScissor) {
+ GL11.glEnable(GL11.GL_SCISSOR_TEST);
+ hasScissor = true;
+ }
+ } else if(hasScissor) {
+ GL11.glDisable(GL11.GL_SCISSOR_TEST);
+ hasScissor = false;
+ }
}
- int glGenTexture() {
- ib16.clear().limit(1);
- GL11.glGenTextures(ib16);
- return ib16.get(0);
+ /**
+ * Retrieves the active clip region from the top of the stack
+ * @param rect the rect coordinates - may not be updated when clipping is disabled
+ * @return true if clipping is active, false if clipping is disabled
+ */
+ public boolean getClipRect(Rect rect) {
+ return clipStack.getClipRect(rect);
}
- void glDeleteTexture(int id) {
- ib16.clear();
- ib16.put(id).flip();
- GL11.glDeleteTextures(ib16);
+ Logger getLogger() {
+ return Logger.getLogger(LWJGLRenderer.class.getName());
+ }
+
+ /**
+ * If the passed value is not a power of 2 then return the next highest power of 2
+ * otherwise the value is returned unchanged.
+ *
+ *
Warren Jr., Henry S. (2002). Hacker's Delight. Addison Wesley. pp. 48. ISBN 978-0201914658
+ *
+ * @param i a non negative number <= 2^31
+ * @return the smallest power of 2 which is >= i
+ */
+ private static int nextPowerOf2(int i) {
+ i--;
+ i |= (i >> 1);
+ i |= (i >> 2);
+ i |= (i >> 4);
+ i |= (i >> 8);
+ i |= (i >> 16);
+ return i+1;
}
private static class SWCursorAnimState implements AnimationState {
private final long[] lastTime;
private final boolean[] active;
- public SWCursorAnimState() {
+ SWCursorAnimState() {
lastTime = new long[3];
active = new boolean[3];
}
diff --git a/twl/src/de/matthiasmann/twl/renderer/lwjgl/LWJGLTexture.java b/twl/src/de/matthiasmann/twl/renderer/lwjgl/LWJGLTexture.java
index b1eda18..3283f2e 100644
--- a/twl/src/de/matthiasmann/twl/renderer/lwjgl/LWJGLTexture.java
+++ b/twl/src/de/matthiasmann/twl/renderer/lwjgl/LWJGLTexture.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2008-2010, Matthias Mann
+ * Copyright (c) 2008-2012, Matthias Mann
*
* All rights reserved.
*
@@ -108,40 +108,53 @@ public LWJGLTexture(LWJGLRenderer renderer, int width, int height,
if(width <= 0 || height <= 0) {
throw new IllegalArgumentException("size <= 0");
}
-
- id = renderer.glGenTexture();
+ Util.checkGLError();
+ id = GL11.glGenTextures();
+ Util.checkGLError();
if(id == 0) {
throw new OpenGLException("failed to allocate texture ID");
}
-
+ Util.checkGLError();
GL11.glBindTexture(GL11.GL_TEXTURE_2D, id);
+ Util.checkGLError();
GL11.glPixelStorei(GL11.GL_UNPACK_ROW_LENGTH, 0);
+ Util.checkGLError();
GL11.glPixelStorei(GL11.GL_UNPACK_ALIGNMENT, 1);
-
+ Util.checkGLError();
if(GLContext.getCapabilities().OpenGL12) {
+ Util.checkGLError();
GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_WRAP_S, GL12.GL_CLAMP_TO_EDGE);
+ Util.checkGLError();
GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_WRAP_T, GL12.GL_CLAMP_TO_EDGE);
} else {
+ Util.checkGLError();
GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_WRAP_S, GL11.GL_CLAMP);
+ Util.checkGLError();
GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_WRAP_T, GL11.GL_CLAMP);
}
-
+ Util.checkGLError();
GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MAG_FILTER, filter.glValue);
+ Util.checkGLError();
GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MIN_FILTER, filter.glValue);
-
+ Util.checkGLError();
this.texWidth = roundUpPOT(width);
+ Util.checkGLError();
this.texHeight = roundUpPOT(height);
-
+ Util.checkGLError();
if(texWidth != width || texHeight != height) {
+ Util.checkGLError();
GL11.glTexImage2D(GL11.GL_TEXTURE_2D, 0,
fmt.glInternalFormat, texWidth, texHeight,
0, fmt.glFormat, GL11.GL_UNSIGNED_BYTE,
(ByteBuffer)null);
- Util.checkGLError();
- GL11.glTexSubImage2D(GL11.GL_TEXTURE_2D, 0,
- 0, 0, width, height, fmt.glFormat,
- GL11.GL_UNSIGNED_BYTE, buf);
+ if(buf != null) {
+ Util.checkGLError();
+ GL11.glTexSubImage2D(GL11.GL_TEXTURE_2D, 0,
+ 0, 0, width, height, fmt.glFormat,
+ GL11.GL_UNSIGNED_BYTE, buf);
+ }
} else {
+ Util.checkGLError();
GL11.glTexImage2D(GL11.GL_TEXTURE_2D, 0,
fmt.glInternalFormat, texWidth, texHeight,
0, fmt.glFormat, GL11.GL_UNSIGNED_BYTE, buf);
@@ -159,7 +172,7 @@ public void destroy() {
if(id != 0) {
// make sure that our texture is not bound when we try to delete it
GL11.glBindTexture(GL11.GL_TEXTURE_2D, 0);
- renderer.glDeleteTexture(id);
+ GL11.glDeleteTextures(id);
id = 0;
}
if(cursors != null) {
@@ -186,7 +199,7 @@ public int getTexHeight() {
return texHeight;
}
- boolean bind(Color color) {
+ public boolean bind(Color color) {
if(id != 0) {
GL11.glBindTexture(GL11.GL_TEXTURE_2D, id);
renderer.tintStack.setColor(color);
@@ -195,7 +208,7 @@ boolean bind(Color color) {
return false;
}
- boolean bind() {
+ public boolean bind() {
if(id != 0) {
GL11.glBindTexture(GL11.GL_TEXTURE_2D, id);
return true;
@@ -203,7 +216,7 @@ boolean bind() {
return false;
}
- public Image getImage(int x, int y, int width, int height, Color tintColor, boolean tiled) {
+ public Image getImage(int x, int y, int width, int height, Color tintColor, boolean tiled, Rotation rotation) {
if(x < 0 || x >= getWidth()) {
throw new IllegalArgumentException("x");
}
@@ -216,10 +229,13 @@ public Image getImage(int x, int y, int width, int height, Color tintColor, bool
if(y + Math.abs(height) > getHeight()) {
throw new IllegalArgumentException("height");
}
- if(tiled && (width <= 0 || height <= 0)) {
- throw new IllegalArgumentException("Tiled rendering requires positive width & height");
+ if(rotation != Rotation.NONE || (tiled && (width < 0 || height < 0))) {
+ return new TextureAreaRotated(this, x, y, width, height, tintColor, tiled, rotation);
+ } else if(tiled) {
+ return new TextureAreaTiled(this, x, y, width, height, tintColor);
+ } else {
+ return new TextureArea(this, x, y, width, height, tintColor);
}
- return new TextureArea(this, x, y, width, height, tintColor, tiled);
}
public MouseCursor createCursor(int x, int y, int width, int height, int hotSpotX, int hotSpotY, Image imageRef) {
diff --git a/twl/src/de/matthiasmann/twl/renderer/lwjgl/PNGDecoder.java b/twl/src/de/matthiasmann/twl/renderer/lwjgl/PNGDecoder.java
deleted file mode 100644
index 1c8086c..0000000
--- a/twl/src/de/matthiasmann/twl/renderer/lwjgl/PNGDecoder.java
+++ /dev/null
@@ -1,94 +0,0 @@
-/*
- * Copyright (c) 2008-2010, Matthias Mann
- *
- * 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 Matthias Mann 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 de.matthiasmann.twl.renderer.lwjgl;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.nio.ByteBuffer;
-
-/**
- * Delegates to de.matthiasmann.twl.utils.PNGDecoder.
- *
- * @author Matthias Mann
- */
-public class PNGDecoder extends de.matthiasmann.twl.utils.PNGDecoder {
-
- public PNGDecoder(InputStream input) throws IOException {
- super(input);
- }
-
- public LWJGLTexture.Format decideTextureFormat(LWJGLTexture.Format fmt) {
- if(fmt == LWJGLTexture.Format.COLOR) {
- fmt = autoColorFormat();
- }
-
- Format pngFormat = super.decideTextureFormat(fmt.getPngFormat());
- if(fmt.pngFormat == pngFormat) {
- return fmt;
- }
-
- switch(pngFormat) {
- case ALPHA:
- return LWJGLTexture.Format.ALPHA;
- case LUMINANCE:
- return LWJGLTexture.Format.LUMINANCE;
- case LUMINANCE_ALPHA:
- return LWJGLTexture.Format.LUMINANCE_ALPHA;
- case RGB:
- return LWJGLTexture.Format.RGB;
- case RGBA:
- return LWJGLTexture.Format.RGBA;
- case BGRA:
- return LWJGLTexture.Format.BGRA;
- case ABGR:
- return LWJGLTexture.Format.ABGR;
- default:
- throw new UnsupportedOperationException("PNGFormat not handled: " + pngFormat);
- }
- }
-
- private LWJGLTexture.Format autoColorFormat() {
- if(hasAlpha()) {
- if(isRGB()) {
- return LWJGLTexture.Format.ABGR;
- } else {
- return LWJGLTexture.Format.LUMINANCE_ALPHA;
- }
- } else if(isRGB()) {
- return LWJGLTexture.Format.ABGR;
- } else {
- return LWJGLTexture.Format.LUMINANCE;
- }
- }
-
- public void decode(ByteBuffer buffer, int stride, LWJGLTexture.Format fmt) throws IOException {
- super.decode(buffer, stride, fmt.getPngFormat());
- }
-}
diff --git a/twl/src/de/matthiasmann/twl/renderer/lwjgl/TextureArea.java b/twl/src/de/matthiasmann/twl/renderer/lwjgl/TextureArea.java
index d79ee55..9e8e478 100644
--- a/twl/src/de/matthiasmann/twl/renderer/lwjgl/TextureArea.java
+++ b/twl/src/de/matthiasmann/twl/renderer/lwjgl/TextureArea.java
@@ -42,26 +42,22 @@
*/
public class TextureArea extends TextureAreaBase implements Image, SupportsDrawRepeat {
- private static final int REPEAT_CACHE_SIZE = 10;
+ protected static final int REPEAT_CACHE_SIZE = 10;
- private final LWJGLTexture texture;
- private final Color tintColor;
- private final boolean tile;
- private int repeatCacheID = -1;
+ protected final LWJGLTexture texture;
+ protected final Color tintColor;
+ protected int repeatCacheID = -1;
- public TextureArea(LWJGLTexture texture, int x, int y, int width, int height,
- Color tintColor, boolean tile) {
+ public TextureArea(LWJGLTexture texture, int x, int y, int width, int height, Color tintColor) {
super(x, y, width, height, texture.getTexWidth(), texture.getTexHeight());
this.texture = texture;
this.tintColor = (tintColor == null) ? Color.WHITE : tintColor;
- this.tile = tile;
}
TextureArea(TextureArea src, Color tintColor) {
super(src);
this.texture = src.texture;
this.tintColor = tintColor;
- this.tile = src.tile;
}
public void draw(AnimationState as, int x, int y) {
@@ -70,13 +66,9 @@ public void draw(AnimationState as, int x, int y) {
public void draw(AnimationState as, int x, int y, int w, int h) {
if(texture.bind(tintColor)) {
- if(tile) {
- drawTiled(x, y, w, h);
- } else {
- GL11.glBegin(GL11.GL_QUADS);
- drawQuad(x, y, w, h);
- GL11.glEnd();
- }
+ GL11.glBegin(GL11.GL_QUADS);
+ drawQuad(x, y, w, h);
+ GL11.glEnd();
}
}
@@ -113,7 +105,7 @@ private void drawRepeatSlow(int x, int y, int width, int height, int repeatCount
GL11.glEnd();
}
- private void drawRepeat(int x, int y, int repeatCountX, int repeatCountY) {
+ protected void drawRepeat(int x, int y, int repeatCountX, int repeatCountY) {
final int w = width;
final int h = height;
GL11.glBegin(GL11.GL_QUADS);
@@ -129,7 +121,7 @@ private void drawRepeat(int x, int y, int repeatCountX, int repeatCountY) {
GL11.glEnd();
}
- private void drawRepeatCached(int x, int y, int repeatCountX, int repeatCountY) {
+ protected void drawRepeatCached(int x, int y, int repeatCountX, int repeatCountY) {
if(repeatCacheID < 0) {
createRepeatCache();
}
@@ -162,65 +154,7 @@ private void drawRepeatCached(int x, int y, int repeatCountX, int repeatCountY)
}
}
- private void drawTiled(int x, int y, int width, int height) {
- int repeatCountX = width / this.width;
- int repeatCountY = height / this.height;
-
- if(repeatCountX < REPEAT_CACHE_SIZE || repeatCountY < REPEAT_CACHE_SIZE) {
- drawRepeat(x, y, repeatCountX, repeatCountY);
- } else {
- drawRepeatCached(x, y, repeatCountX, repeatCountY);
- }
-
- int drawnX = repeatCountX * this.width;
- int drawnY = repeatCountY * this.height;
- int restWidth = width - drawnX;
- int restHeight = height - drawnY;
- if(restWidth > 0 || restHeight > 0) {
- GL11.glBegin(GL11.GL_QUADS);
- if(restWidth > 0 && repeatCountY > 0) {
- drawClipped(x + drawnX, y, restWidth, this.height, 1, repeatCountY);
- }
- if(restHeight > 0) {
- if(repeatCountX > 0) {
- drawClipped(x, y + drawnY, this.width, restHeight, repeatCountX, 1);
- }
- if(restWidth > 0) {
- drawClipped(x + drawnX, y + drawnY, restWidth, restHeight, 1, 1);
- }
- }
- GL11.glEnd();
- }
- }
-
- private void drawClipped(int x, int y, int width, int height, int repeatCountX, int repeatCountY) {
- float ctx0 = tx0;
- float cty0 = ty0;
- float ctx1 = tx1;
- float cty1 = ty1;
- if(this.width > 1) {
- ctx1 = ctx0 + width / (float)texture.getTexWidth();
- }
- if(this.height > 1) {
- cty1 = cty0 + height / (float)texture.getTexHeight();
- }
-
- while(repeatCountY-- > 0) {
- int y1 = y + height;
- int x0 = x;
- for(int cx=repeatCountX ; cx-- > 0 ;) {
- int x1 = x0 + width;
- GL11.glTexCoord2f(ctx0, cty0); GL11.glVertex2i(x0, y );
- GL11.glTexCoord2f(ctx0, cty1); GL11.glVertex2i(x0, y1);
- GL11.glTexCoord2f(ctx1, cty1); GL11.glVertex2i(x1, y1);
- GL11.glTexCoord2f(ctx1, cty0); GL11.glVertex2i(x1, y );
- x0 = x1;
- }
- y = y1;
- }
- }
-
- private void createRepeatCache() {
+ protected void createRepeatCache() {
repeatCacheID = GL11.glGenLists(1);
texture.renderer.textureAreas.add(this);
@@ -233,14 +167,6 @@ void destroyRepeatCache() {
GL11.glDeleteLists(repeatCacheID, 1);
repeatCacheID = -1;
}
-
- int getX() {
- return (int)(tx0 * texture.getTexWidth());
- }
-
- int getY() {
- return (int)(ty0 * texture.getTexHeight());
- }
public Image createTintedVersion(Color color) {
if(color == null) {
diff --git a/twl/src/de/matthiasmann/twl/renderer/lwjgl/TextureAreaBase.java b/twl/src/de/matthiasmann/twl/renderer/lwjgl/TextureAreaBase.java
index 4199399..2580162 100644
--- a/twl/src/de/matthiasmann/twl/renderer/lwjgl/TextureAreaBase.java
+++ b/twl/src/de/matthiasmann/twl/renderer/lwjgl/TextureAreaBase.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2008, Matthias Mann
+ * Copyright (c) 2008-2012, Matthias Mann
*
* All rights reserved.
*
@@ -51,13 +51,17 @@ public class TextureAreaBase {
this.height = (short)Math.abs(height);
float fx = x;
float fy = y;
- if(width == 1) {
+ if(width == 1 || width == -1) {
fx += 0.5f;
width = 0;
+ } else if(width < 0) {
+ fx -= width;
}
- if(height == 1) {
+ if(height == 1 || height == -1) {
fy += 0.5f;
height = 0;
+ } else if(height < 0) {
+ fy -= height;
}
this.tx0 = fx / texWidth;
this.ty0 = fy / texHeight;
diff --git a/twl/src/de/matthiasmann/twl/renderer/lwjgl/TextureAreaRotated.java b/twl/src/de/matthiasmann/twl/renderer/lwjgl/TextureAreaRotated.java
new file mode 100644
index 0000000..4e19052
--- /dev/null
+++ b/twl/src/de/matthiasmann/twl/renderer/lwjgl/TextureAreaRotated.java
@@ -0,0 +1,319 @@
+/*
+ * Copyright (c) 2008-2011, Matthias Mann
+ *
+ * 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 Matthias Mann 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 de.matthiasmann.twl.renderer.lwjgl;
+
+import de.matthiasmann.twl.Color;
+import de.matthiasmann.twl.renderer.AnimationState;
+import de.matthiasmann.twl.renderer.Image;
+import de.matthiasmann.twl.renderer.Texture;
+import org.lwjgl.opengl.GL11;
+
+/**
+ * A rotated, tiled or flipped area inside a OpenGL texture used as UI image
+ *
+ * @author Matthias Mann
+ */
+public class TextureAreaRotated implements Image {
+
+ protected static final int REPEAT_CACHE_SIZE = 10;
+
+ private final LWJGLTexture texture;
+ private final Color tintColor;
+ private final float txTL;
+ private final float tyTL;
+ private final float txTR;
+ private final float tyTR;
+ private final float txBL;
+ private final float tyBL;
+ private final float txBR;
+ private final float tyBR;
+ private final char width;
+ private final char height;
+ private final boolean tiled;
+ protected int repeatCacheID = -1;
+
+ public TextureAreaRotated(LWJGLTexture texture, int x, int y, int width, int height,
+ Color tintColor, boolean tiled, Texture.Rotation rotation) {
+ // negative size allows for flipping
+ if(rotation == Texture.Rotation.CLOCKWISE_90 || rotation == Texture.Rotation.CLOCKWISE_270) {
+ this.width = (char)Math.abs(height);
+ this.height = (char)Math.abs(width);
+ } else {
+ this.width = (char)Math.abs(width);
+ this.height = (char)Math.abs(height);
+ }
+
+ float fx = x;
+ float fy = y;
+ if(width == 1) {
+ fx += 0.5f;
+ width = 0;
+ } else if(width < -1) {
+ fx -= width + 1;
+ }
+ if(height == 1) {
+ fy += 0.5f;
+ height = 0;
+ } else if(height < -1) {
+ fy -= height + 1;
+ }
+
+ float texWidth = texture.getTexWidth();
+ float texHeight = texture.getTexHeight();
+
+ float tx0 = fx / texWidth;
+ float ty0 = fy / texHeight;
+ float tx1 = tx0 + (width / texWidth);
+ float ty1 = ty0 + (height / texHeight);
+
+ switch(rotation) {
+ default:
+ txTL = txBL = tx0;
+ txTR = txBR = tx1;
+ tyTL = tyTR = ty0;
+ tyBL = tyBR = ty1;
+ break;
+ case CLOCKWISE_90:
+ txTL = tx0; tyTL = ty1;
+ txTR = tx0; tyTR = ty0;
+ txBL = tx1; tyBL = ty1;
+ txBR = tx1; tyBR = ty0;
+ break;
+ case CLOCKWISE_180:
+ txTL = tx1; tyTL = ty1;
+ txTR = tx0; tyTR = ty1;
+ txBL = tx1; tyBL = ty0;
+ txBR = tx0; tyBR = ty0;
+ break;
+ case CLOCKWISE_270:
+ txTL = tx1; tyTL = ty0;
+ txTR = tx1; tyTR = ty1;
+ txBL = tx0; tyBL = ty0;
+ txBR = tx0; tyBR = ty1;
+ break;
+ }
+ this.texture = texture;
+ this.tintColor = (tintColor == null) ? Color.WHITE : tintColor;
+ this.tiled = tiled;
+ }
+
+ TextureAreaRotated(TextureAreaRotated src, Color tintColor) {
+ this.txTL = src.txTL;
+ this.tyTL = src.tyTL;
+ this.txTR = src.txTR;
+ this.tyTR = src.tyTR;
+ this.txBL = src.txBL;
+ this.tyBL = src.tyBL;
+ this.txBR = src.txBR;
+ this.tyBR = src.tyBR;
+ this.width = src.width;
+ this.height = src.height;
+ this.texture = src.texture;
+ this.tiled = src.tiled;
+ this.tintColor = tintColor;
+ }
+
+ public int getHeight() {
+ return height;
+ }
+
+ public int getWidth() {
+ return width;
+ }
+
+ public void draw(AnimationState as, int x, int y) {
+ draw(as, x, y, width, height);
+ }
+
+ public void draw(AnimationState as, int x, int y, int w, int h) {
+ if(texture.bind(tintColor)) {
+ if(tiled) {
+ drawTiled(x, y, w, h);
+ } else {
+ GL11.glBegin(GL11.GL_QUADS);
+ drawQuad(x, y, w, h);
+ GL11.glEnd();
+ }
+ }
+ }
+
+ private void drawRepeat(int x, int y, int repeatCountX, int repeatCountY) {
+ GL11.glBegin(GL11.GL_QUADS);
+ final int w = width;
+ final int h = height;
+ while(repeatCountY-- > 0) {
+ int curX = x;
+ int cntX = repeatCountX;
+ while(cntX-- > 0) {
+ drawQuad(curX, y, w, h);
+ curX += w;
+ }
+ y += h;
+ }
+ GL11.glEnd();
+ }
+
+ private void drawTiled(int x, int y, int w, int h) {
+ int repeatCountX = w / this.width;
+ int repeatCountY = h / this.height;
+
+ if(repeatCountX < REPEAT_CACHE_SIZE || repeatCountY < REPEAT_CACHE_SIZE) {
+ drawRepeat(x, y, repeatCountX, repeatCountY);
+ } else {
+ drawRepeatCached(x, y, repeatCountX, repeatCountY);
+ }
+
+ int drawnX = repeatCountX * this.width;
+ int drawnY = repeatCountY * this.height;
+ int restWidth = w - drawnX;
+ int restHeight = h - drawnY;
+ if(restWidth > 0 || restHeight > 0) {
+ GL11.glBegin(GL11.GL_QUADS);
+ if(restWidth > 0 && repeatCountY > 0) {
+ drawClipped(x + drawnX, y, restWidth, this.height, 1, repeatCountY);
+ }
+ if(restHeight > 0) {
+ if(repeatCountX > 0) {
+ drawClipped(x, y + drawnY, this.width, restHeight, repeatCountX, 1);
+ }
+ if(restWidth > 0) {
+ drawClipped(x + drawnX, y + drawnY, restWidth, restHeight, 1, 1);
+ }
+ }
+ GL11.glEnd();
+ }
+ }
+
+
+ protected void drawRepeatCached(int x, int y, int repeatCountX, int repeatCountY) {
+ if(repeatCacheID < 0) {
+ createRepeatCache();
+ }
+
+ int cacheBlocksX = repeatCountX / REPEAT_CACHE_SIZE;
+ int repeatsByCacheX = cacheBlocksX * REPEAT_CACHE_SIZE;
+
+ if(repeatCountX > repeatsByCacheX) {
+ drawRepeat(x + width * repeatsByCacheX, y,
+ repeatCountX - repeatsByCacheX, repeatCountY);
+ }
+
+ do {
+ GL11.glPushMatrix();
+ GL11.glTranslatef(x, y, 0f);
+ GL11.glCallList(repeatCacheID);
+
+ for(int i=1 ; i= REPEAT_CACHE_SIZE);
+
+ if(repeatCountY > 0) {
+ drawRepeat(x, y, repeatsByCacheX, repeatCountY);
+ }
+ }
+
+ private void drawClipped(int x, int y, int width, int height, int repeatCountX, int repeatCountY) {
+ float ctxTL = txTL;
+ float ctyTL = tyTL;
+ float ctxTR = txTR;
+ float ctyTR = tyTR;
+ float ctxBL = txBL;
+ float ctyBL = tyBL;
+ float ctxBR = txBR;
+ float ctyBR = tyBR;
+
+ if(this.width > 1) {
+ float f = width / (float)this.width;
+ ctxTR = ctxTL + (ctxTR - ctxTL) * f;
+ ctyTR = ctyTL + (ctyTR - ctyTL) * f;
+ ctxBR = ctxBL + (ctxBR - ctxBL) * f;
+ ctyBR = ctyBL + (ctyBR - ctyBL) * f;
+ }
+ if(this.height > 1) {
+ float f = height / (float)this.height;
+ ctxBL = ctxTL + (ctxBL - ctxTL) * f;
+ ctyBL = ctyTL + (ctyBL - ctyTL) * f;
+ ctxBR = ctxTR + (ctxBR - ctxTR) * f;
+ ctyBR = ctyTR + (ctyBR - ctyTR) * f;
+ }
+
+ while(repeatCountY-- > 0) {
+ int y1 = y + height;
+ int x0 = x;
+ for(int cx=repeatCountX ; cx-- > 0 ;) {
+ int x1 = x0 + width;
+ GL11.glTexCoord2f(ctxTL, ctyTL); GL11.glVertex2i(x0, y );
+ GL11.glTexCoord2f(ctxBL, ctyBL); GL11.glVertex2i(x0, y1);
+ GL11.glTexCoord2f(ctxBR, ctyBR); GL11.glVertex2i(x1, y1);
+ GL11.glTexCoord2f(ctxTR, ctyTR); GL11.glVertex2i(x1, y );
+ x0 = x1;
+ }
+ y = y1;
+ }
+ }
+
+ private void drawQuad(int x, int y, int w, int h) {
+ GL11.glTexCoord2f(txTL, tyTL); GL11.glVertex2i(x , y );
+ GL11.glTexCoord2f(txBL, tyBL); GL11.glVertex2i(x , y + h);
+ GL11.glTexCoord2f(txBR, tyBR); GL11.glVertex2i(x + w, y + h);
+ GL11.glTexCoord2f(txTR, tyTR); GL11.glVertex2i(x + w, y );
+ }
+
+ private void createRepeatCache() {
+ repeatCacheID = GL11.glGenLists(1);
+ texture.renderer.rotatedTextureAreas.add(this);
+
+ GL11.glNewList(repeatCacheID, GL11.GL_COMPILE);
+ drawRepeat(0, 0, REPEAT_CACHE_SIZE, REPEAT_CACHE_SIZE);
+ GL11.glEndList();
+ }
+
+ void destroyRepeatCache() {
+ GL11.glDeleteLists(repeatCacheID, 1);
+ repeatCacheID = -1;
+ }
+
+ public Image createTintedVersion(Color color) {
+ if(color == null) {
+ throw new NullPointerException("color");
+ }
+ Color newTintColor = tintColor.multiply(color);
+ if(newTintColor.equals(tintColor)) {
+ return this;
+ }
+ return new TextureAreaRotated(this, newTintColor);
+ }
+}
diff --git a/twl/src/de/matthiasmann/twl/renderer/lwjgl/TextureAreaTiled.java b/twl/src/de/matthiasmann/twl/renderer/lwjgl/TextureAreaTiled.java
new file mode 100644
index 0000000..f7cf55b
--- /dev/null
+++ b/twl/src/de/matthiasmann/twl/renderer/lwjgl/TextureAreaTiled.java
@@ -0,0 +1,124 @@
+/*
+ * Copyright (c) 2008-2011, Matthias Mann
+ *
+ * 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 Matthias Mann 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 de.matthiasmann.twl.renderer.lwjgl;
+
+import de.matthiasmann.twl.Color;
+import de.matthiasmann.twl.renderer.AnimationState;
+import de.matthiasmann.twl.renderer.Image;
+import org.lwjgl.opengl.GL11;
+
+/**
+ * A tiled area inside a OpenGL texture used as UI image
+ *
+ * @author Matthias Mann
+ */
+public class TextureAreaTiled extends TextureArea {
+
+ public TextureAreaTiled(LWJGLTexture texture, int x, int y, int width, int height, Color tintColor) {
+ super(texture, x, y, width, height, tintColor);
+ }
+
+ TextureAreaTiled(TextureAreaTiled src, Color tintColor) {
+ super(src, tintColor);
+ }
+
+ @Override
+ public void draw(AnimationState as, int x, int y, int w, int h) {
+ if(texture.bind(tintColor)) {
+ int repeatCountX = w / this.width;
+ int repeatCountY = h / this.height;
+
+ if(repeatCountX < REPEAT_CACHE_SIZE || repeatCountY < REPEAT_CACHE_SIZE) {
+ drawRepeat(x, y, repeatCountX, repeatCountY);
+ } else {
+ drawRepeatCached(x, y, repeatCountX, repeatCountY);
+ }
+
+ int drawnX = repeatCountX * this.width;
+ int drawnY = repeatCountY * this.height;
+ int restWidth = w - drawnX;
+ int restHeight = h - drawnY;
+ if(restWidth > 0 || restHeight > 0) {
+ GL11.glBegin(GL11.GL_QUADS);
+ if(restWidth > 0 && repeatCountY > 0) {
+ drawClipped(x + drawnX, y, restWidth, this.height, 1, repeatCountY);
+ }
+ if(restHeight > 0) {
+ if(repeatCountX > 0) {
+ drawClipped(x, y + drawnY, this.width, restHeight, repeatCountX, 1);
+ }
+ if(restWidth > 0) {
+ drawClipped(x + drawnX, y + drawnY, restWidth, restHeight, 1, 1);
+ }
+ }
+ GL11.glEnd();
+ }
+ }
+ }
+
+ private void drawClipped(int x, int y, int width, int height, int repeatCountX, int repeatCountY) {
+ float ctx0 = tx0;
+ float cty0 = ty0;
+ float ctx1 = tx1;
+ float cty1 = ty1;
+ if(this.width > 1) {
+ ctx1 = ctx0 + width / (float)texture.getTexWidth();
+ }
+ if(this.height > 1) {
+ cty1 = cty0 + height / (float)texture.getTexHeight();
+ }
+
+ while(repeatCountY-- > 0) {
+ int y1 = y + height;
+ int x0 = x;
+ for(int cx=repeatCountX ; cx-- > 0 ;) {
+ int x1 = x0 + width;
+ GL11.glTexCoord2f(ctx0, cty0); GL11.glVertex2i(x0, y );
+ GL11.glTexCoord2f(ctx0, cty1); GL11.glVertex2i(x0, y1);
+ GL11.glTexCoord2f(ctx1, cty1); GL11.glVertex2i(x1, y1);
+ GL11.glTexCoord2f(ctx1, cty0); GL11.glVertex2i(x1, y );
+ x0 = x1;
+ }
+ y = y1;
+ }
+ }
+
+ @Override
+ public Image createTintedVersion(Color color) {
+ if(color == null) {
+ throw new NullPointerException("color");
+ }
+ Color newTintColor = tintColor.multiply(color);
+ if(newTintColor.equals(tintColor)) {
+ return this;
+ }
+ return new TextureAreaTiled(this, newTintColor);
+ }
+}
diff --git a/twl/src/de/matthiasmann/twl/renderer/lwjgl/TintStack.java b/twl/src/de/matthiasmann/twl/renderer/lwjgl/TintStack.java
index e440fd8..135366a 100644
--- a/twl/src/de/matthiasmann/twl/renderer/lwjgl/TintStack.java
+++ b/twl/src/de/matthiasmann/twl/renderer/lwjgl/TintStack.java
@@ -45,6 +45,7 @@ public class TintStack {
TintStack next;
float r,g,b,a;
+ @SuppressWarnings("LeakingThisInConstructor")
public TintStack() {
this.prev = this;
this.r = ONE_OVER_255;
@@ -56,7 +57,18 @@ public TintStack() {
private TintStack(TintStack prev) {
this.prev = prev;
}
-
+
+ public TintStack pushReset() {
+ if(next == null) {
+ next = new TintStack(this);
+ }
+ next.r = ONE_OVER_255;
+ next.g = ONE_OVER_255;
+ next.b = ONE_OVER_255;
+ next.a = ONE_OVER_255;
+ return next;
+ }
+
public TintStack push(float r, float g, float b, float a) {
if(next == null) {
next = new TintStack(this);
@@ -67,7 +79,15 @@ public TintStack push(float r, float g, float b, float a) {
next.a = this.a * a;
return next;
}
-
+
+ public TintStack push(Color color) {
+ return push(
+ color.getRedFloat(),
+ color.getGreenFloat(),
+ color.getBlueFloat(),
+ color.getAlphaFloat());
+ }
+
public TintStack pop() {
return prev;
}
@@ -95,9 +115,25 @@ public float getA() {
*/
public void setColor(Color color) {
GL11.glColor4f(
- r*(color.getR()&255),
- g*(color.getG()&255),
- b*(color.getB()&255),
- a*(color.getA()&255));
+ r*color.getRed(),
+ g*color.getGreen(),
+ b*color.getBlue(),
+ a*color.getAlpha());
+ }
+
+ /**
+ * GL11.glColor4f(color * tint);
+ *
+ * @param r the red component 0..255
+ * @param g the green component 0..255
+ * @param b the blue component 0..255
+ * @param a the alpha component 0..255
+ */
+ public void setColor(float r, float g, float b, float a) {
+ GL11.glColor4f(
+ this.r * r,
+ this.g * g,
+ this.b * b,
+ this.a * a);
}
}
diff --git a/twl/src/de/matthiasmann/twl/renderer/lwjgl/VertexArray.java b/twl/src/de/matthiasmann/twl/renderer/lwjgl/VertexArray.java
new file mode 100644
index 0000000..a5535eb
--- /dev/null
+++ b/twl/src/de/matthiasmann/twl/renderer/lwjgl/VertexArray.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright (c) 2008-2012, Matthias Mann
+ *
+ * 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 Matthias Mann 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 de.matthiasmann.twl.renderer.lwjgl;
+
+import java.nio.FloatBuffer;
+import org.lwjgl.BufferUtils;
+import org.lwjgl.opengl.GL11;
+
+/**
+ * Simple vertex array class.
+ *
+ *
This class manages an interleaved vertex array in float format: {@code tx, ty, x, y}
+ *
+ * @author Matthias Mann
+ */
+public class VertexArray {
+
+ private FloatBuffer va;
+
+ public FloatBuffer allocate(int maxQuads) {
+ int capacity = 4 * 4 * maxQuads;
+ if(va == null || va.capacity() < capacity) {
+ va = BufferUtils.createFloatBuffer(capacity);
+ }
+ va.clear();
+ return va;
+ }
+
+ public void bind() {
+ va.position(2);
+ GL11.glVertexPointer(2, 4*4, va);
+ va.position(0);
+ GL11.glTexCoordPointer(2, 4*4, va);
+ GL11.glEnableClientState(GL11.GL_VERTEX_ARRAY);
+ GL11.glEnableClientState(GL11.GL_TEXTURE_COORD_ARRAY);
+ }
+
+ public void drawVertices(int start, int count) {
+ GL11.glDrawArrays(GL11.GL_QUADS, start, count);
+ }
+
+ public void drawQuads(int start, int count) {
+ GL11.glDrawArrays(GL11.GL_QUADS, start*4, count*4);
+ }
+
+ public void unbind() {
+ GL11.glDisableClientState(GL11.GL_VERTEX_ARRAY);
+ GL11.glDisableClientState(GL11.GL_TEXTURE_COORD_ARRAY);
+ }
+
+}
diff --git a/twl/src/de/matthiasmann/twl/textarea/CSSStyle.java b/twl/src/de/matthiasmann/twl/textarea/CSSStyle.java
index 5cc33d8..82b6b6b 100644
--- a/twl/src/de/matthiasmann/twl/textarea/CSSStyle.java
+++ b/twl/src/de/matthiasmann/twl/textarea/CSSStyle.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2008-2010, Matthias Mann
+ * Copyright (c) 2008-2012, Matthias Mann
*
* All rights reserved.
*
@@ -29,6 +29,8 @@
*/
package de.matthiasmann.twl.textarea;
+import de.matthiasmann.twl.utils.StringList;
+import de.matthiasmann.twl.Color;
import de.matthiasmann.twl.utils.ParameterStringParser;
import de.matthiasmann.twl.utils.TextUtil;
import java.util.HashMap;
@@ -77,18 +79,30 @@ protected void parseCSSAttribute(String key, String value) {
parseBox(key.substring(7), value, StyleAttribute.PADDING);
return;
}
+ if(key.startsWith("font")) {
+ parseFont(key, value);
+ return;
+ }
if("text-indent".equals(key)) {
- parseValueUnit(StyleAttribute.TEXT_IDENT, value);
+ parseValueUnit(StyleAttribute.TEXT_INDENT, value);
+ return;
+ }
+ if("-twl-font".equals(key)) {
+ put(StyleAttribute.FONT_FAMILIES, new StringList(value));
return;
}
- if("font-family".equals(key) || "font".equals(key)) {
- put(StyleAttribute.FONT_NAME, value);
+ if("-twl-hover".equals(key)) {
+ parseEnum(StyleAttribute.INHERIT_HOVER, INHERITHOVER, value);
return;
}
if("text-align".equals(key)) {
parseEnum(StyleAttribute.HORIZONTAL_ALIGNMENT, value);
return;
}
+ if("text-decoration".equals(key)) {
+ parseEnum(StyleAttribute.TEXT_DECORATION, TEXTDECORATION, value);
+ return;
+ }
if("vertical-align".equals(key)) {
parseEnum(StyleAttribute.VERTICAL_ALIGNMENT, value);
return;
@@ -133,6 +147,18 @@ protected void parseCSSAttribute(String key, String value) {
parseURL(StyleAttribute.BACKGROUND_IMAGE, value);
return;
}
+ if("background-color".equals(key) || "-twl-background-color".equals(key)) {
+ parseColor(StyleAttribute.BACKGROUND_COLOR, value);
+ return;
+ }
+ if("color".equals(key)) {
+ parseColor(StyleAttribute.COLOR, value);
+ return;
+ }
+ if("tab-size".equals(key) || "-moz-tab-size".equals(key)) {
+ parseInteger(StyleAttribute.TAB_SIZE, value);
+ return;
+ }
throw new IllegalArgumentException("Unsupported key: " + key);
}
@@ -177,12 +203,48 @@ private void parseBox(String key, String value, BoxAttribute box) {
}
}
}
-
+
+ private void parseFont(String key, String value) {
+ if("font-family".equals(key)) {
+ parseList(StyleAttribute.FONT_FAMILIES, value);
+ return;
+ }
+ if("font-weight".equals(key)) {
+ Integer weight = WEIGHTS.get(value);
+ if(weight == null) {
+ weight = Integer.valueOf(value);
+ }
+ put(StyleAttribute.FONT_WEIGHT, weight);
+ return;
+ }
+ if("font-size".equals(key)) {
+ parseValueUnit(StyleAttribute.FONT_SIZE, value);
+ return;
+ }
+ if("font-style".equals(key)) {
+ parseEnum(StyleAttribute.FONT_ITALIC, ITALIC, value);
+ return;
+ }
+ if("font".equals(key)) {
+ value = parseStartsWith(StyleAttribute.FONT_WEIGHT, WEIGHTS, value);
+ value = parseStartsWith(StyleAttribute.FONT_ITALIC, ITALIC, value);
+ if(value.length() > 0 && Character.isDigit(value.charAt(0))) {
+ int end = TextUtil.indexOf(value, ' ', 0);
+ parseValueUnit(StyleAttribute.FONT_SIZE, value.substring(0, end));
+ end = TextUtil.skipSpaces(value, end);
+ value = value.substring(end);
+ }
+ parseList(StyleAttribute.FONT_FAMILIES, value);
+ }
+ }
+
private Value parseValueUnit(String value) {
Value.Unit unit;
int suffixLength = 2;
if(value.endsWith("px")) {
unit = Value.Unit.PX;
+ } else if(value.endsWith("pt")) {
+ unit = Value.Unit.PT;
} else if(value.endsWith("em")) {
unit = Value.Unit.EM;
} else if(value.endsWith("ex")) {
@@ -198,7 +260,7 @@ private Value parseValueUnit(String value) {
throw new IllegalArgumentException("Unknown numeric suffix: " + value);
}
- String numberPart = value.substring(0, value.length() - suffixLength).trim();
+ String numberPart = TextUtil.trim(value, 0, value.length() - suffixLength);
return new Value(Float.parseFloat(numberPart), unit);
}
@@ -215,6 +277,15 @@ private void parseValueUnit(StyleAttribute> attribute, String value) {
put(attribute, parseValueUnit(value));
}
+ private void parseInteger(StyleAttribute attribute, String value) {
+ if("inherit".equals(value)) {
+ put(attribute, null);
+ } else {
+ int intval = Integer.parseInt(value);
+ put(attribute, intval);
+ }
+ }
+
private void parseEnum(StyleAttribute attribute, HashMap map, String value) {
T obj = map.get(value);
if(obj == null) {
@@ -227,21 +298,125 @@ private> void parseEnum(StyleAttribute attribute, String va
E obj = Enum.valueOf(attribute.getDataType(), value.toUpperCase(Locale.ENGLISH));
put(attribute, obj);
}
+
+ private String parseStartsWith(StyleAttribute attribute, HashMap map, String value) {
+ int end = TextUtil.indexOf(value, ' ', 0);
+ E obj = map.get(value.substring(0, end));
+ if(obj != null) {
+ end = TextUtil.skipSpaces(value, end);
+ value = value.substring(end);
+ }
+ put(attribute, obj);
+ return value;
+ }
private void parseURL(StyleAttribute attribute, String value) {
+ put(attribute, stripURL(value));
+ }
+
+ static String stripTrim(String value, int start, int end) {
+ return TextUtil.trim(value, start, value.length() - end);
+ }
+
+ static String stripURL(String value) {
if(value.startsWith("url(") && value.endsWith(")")) {
- value = value.substring(4, value.length() - 1).trim();
- if((value.startsWith("\"") && value.endsWith("\"")) ||
- (value.startsWith("'") && value.endsWith("'"))) {
- value = value.substring(1, value.length() - 1);
+ value = stripQuotes(stripTrim(value, 4, 1));
+ }
+ return value;
+ }
+
+ static String stripQuotes(String value) {
+ if((value.startsWith("\"") && value.endsWith("\"")) ||
+ (value.startsWith("'") && value.endsWith("'"))) {
+ value = value.substring(1, value.length() - 1);
+ }
+ return value;
+ }
+
+ private void parseColor(StyleAttribute attribute, String value) {
+ Color color;
+ if(value.startsWith("rgb(") && value.endsWith(")")) {
+ value = stripTrim(value, 4, 1);
+ byte[] rgb = parseRGBA(value, 3);
+ color = new Color(rgb[0], rgb[1], rgb[2], (byte)255);
+ } else if(value.startsWith("rgba(") && value.endsWith(")")) {
+ value = stripTrim(value, 5, 1);
+ byte[] rgba = parseRGBA(value, 4);
+ color = new Color(rgba[0], rgba[1], rgba[2], rgba[3]);
+ } else {
+ color = Color.parserColor(value);
+ if(color == null) {
+ throw new IllegalArgumentException("unknown color name: " + value);
+ }
+ }
+ put(attribute, color);
+ }
+
+ private byte[] parseRGBA(String value, int numElements) {
+ String[] parts = value.split(",");
+ if(parts.length != numElements) {
+ throw new IllegalArgumentException("3 values required for rgb()");
+ }
+ byte[] rgba = new byte[numElements];
+ for(int i=0 ; i attribute, String value) {
+ put(attribute, parseList(value, 0));
+ }
+
+ static StringList parseList(String value, int idx) {
+ idx = TextUtil.skipSpaces(value, idx);
+ if(idx >= value.length()) {
+ return null;
+ }
+
+ char startChar = value.charAt(idx);
+ int end;
+ String part;
+
+ if(startChar == '"' || startChar == '\'') {
+ ++idx;
+ end = TextUtil.indexOf(value, startChar, idx);
+ part = value.substring(idx, end);
+ end = TextUtil.skipSpaces(value, ++end);
+ if(end < value.length() && value.charAt(end) != ',') {
+ throw new IllegalArgumentException("',' expected at " + idx);
}
+ } else {
+ end = TextUtil.indexOf(value, ',', idx);
+ part = TextUtil.trim(value, idx, end);
}
- put(attribute, value);
+
+ return new StringList(part, parseList(value, end+1));
}
static final HashMap PRE = new HashMap();
static final HashMap BREAKWORD = new HashMap();
static final HashMap OLT = new HashMap();
+ static final HashMap ITALIC = new HashMap();
+ static final HashMap WEIGHTS = new HashMap();
+ static final HashMap TEXTDECORATION = new HashMap();
+ static final HashMap INHERITHOVER = new HashMap();
static OrderedListType createRoman(final boolean lowercase) {
return new OrderedListType() {
@@ -278,5 +453,19 @@ public String format(int nr) {
OLT.put("lower-norwegian", new OrderedListType("abcdefghijklmnopqrstuvwxyzæøå"));
OLT.put("upper-russian-short", new OrderedListType("АБВГДЕЖЗИКЛМНОПРСТУФХЦЧШЩЭЮЯ"));
OLT.put("lower-russian-short", new OrderedListType("абвгдежзиклмнопрстуфхцчшщэюя"));
+
+ ITALIC.put("normal", Boolean.FALSE);
+ ITALIC.put("italic", Boolean.TRUE);
+ ITALIC.put("oblique", Boolean.TRUE);
+
+ WEIGHTS.put("normal", 400);
+ WEIGHTS.put("bold", 700);
+
+ TEXTDECORATION.put("none", TextDecoration.NONE);
+ TEXTDECORATION.put("underline", TextDecoration.UNDERLINE);
+ TEXTDECORATION.put("line-through", TextDecoration.LINE_THROUGH);
+
+ INHERITHOVER.put("inherit", Boolean.TRUE);
+ INHERITHOVER.put("normal", Boolean.FALSE);
}
}
diff --git a/twl/src/de/matthiasmann/twl/textarea/HTMLTextAreaModel.java b/twl/src/de/matthiasmann/twl/textarea/HTMLTextAreaModel.java
index a42e08b..9c72489 100644
--- a/twl/src/de/matthiasmann/twl/textarea/HTMLTextAreaModel.java
+++ b/twl/src/de/matthiasmann/twl/textarea/HTMLTextAreaModel.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2008-2010, Matthias Mann
+ * Copyright (c) 2008-2012, Matthias Mann
*
* All rights reserved.
*
@@ -32,6 +32,7 @@
import de.matthiasmann.twl.model.HasCallback;
import de.matthiasmann.twl.utils.MultiStringReader;
import de.matthiasmann.twl.utils.TextUtil;
+import de.matthiasmann.twl.utils.XMLParser;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
@@ -45,7 +46,6 @@
import java.util.logging.Logger;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
-import org.xmlpull.v1.XmlPullParserFactory;
/**
* A simple XHTML parser.
@@ -120,6 +120,7 @@ public HTMLTextAreaModel() {
* @param html the HTML to parse
* @see #setHtml(java.lang.String)
*/
+ @SuppressWarnings("OverridableMethodCallInConstructor")
public HTMLTextAreaModel(String html) {
this();
setHtml(html);
@@ -133,6 +134,7 @@ public HTMLTextAreaModel(String html) {
* @param r the reader to parse html from
* @throws IOException if an error occured while reading
*/
+ @SuppressWarnings("OverridableMethodCallInConstructor")
public HTMLTextAreaModel(Reader r) throws IOException {
this();
parseXHTML(r);
@@ -225,10 +227,7 @@ public void parseXHTML(Reader reader) {
this.title = null;
try {
- XmlPullParserFactory xppf = XmlPullParserFactory.newInstance();
- xppf.setNamespaceAware(false);
- xppf.setValidating(false);
- XmlPullParser xpp = xppf.newPullParser();
+ XmlPullParser xpp = XMLParser.createParser();
xpp.setInput(reader);
xpp.defineEntityReplacementText("nbsp", "\u00A0");
xpp.require(XmlPullParser.START_DOCUMENT, null, null);
@@ -305,7 +304,7 @@ private void parseMain(XmlPullParser xpp) throws XmlPullParserException, IOExcep
String btnName = TextUtil.notNull(xpp.getAttributeValue(null, "name"));
String btnParam = TextUtil.notNull(xpp.getAttributeValue(null, "value"));
element = new WidgetElement(style, btnName, btnParam);
- } else if("ul".equals(name) || "h1".equals(name)) {
+ } else if("ul".equals(name)) {
ContainerElement ce = new ContainerElement(style);
parseContainer(xpp, ce);
element = ce;
@@ -318,7 +317,7 @@ private void parseMain(XmlPullParser xpp) throws XmlPullParserException, IOExcep
parseContainer(xpp, le);
element = le;
--level;
- } else if("div".equals(name)) {
+ } else if("div".equals(name) || isHeading(name)) {
BlockElement be = new BlockElement(style);
parseContainer(xpp, be);
element = be;
@@ -443,7 +442,7 @@ private TableElement parseTable(XmlPullParser xpp, Style tableStyle) throws XmlP
tableElement.setRowStyle(row, rowStyles.get(row));
for(int col=0 ; col= '0' && name.charAt(1) <= '6');
+ }
+
private boolean isPre() {
return getStyle().get(StyleAttribute.PREFORMATTED, null);
}
diff --git a/twl/src/de/matthiasmann/twl/textarea/Parser.java b/twl/src/de/matthiasmann/twl/textarea/Parser.java
index 5efb22a..cbfae9f 100644
--- a/twl/src/de/matthiasmann/twl/textarea/Parser.java
+++ b/twl/src/de/matthiasmann/twl/textarea/Parser.java
@@ -1,613 +1,618 @@
-/* The following code was generated by JFlex 1.4.3 on 18.11.10 18:09 */
-
-/*
- * Copyright (c) 2008-2010, Matthias Mann
- *
- * 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 Matthias Mann 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 de.matthiasmann.twl.textarea;
-
-
-/**
- * This class is a scanner generated by
- * JFlex 1.4.3
- * on 18.11.10 18:09 from the specification file
- * parser.flex
- */
-class Parser {
-
- /** This character denotes the end of file */
- public static final int YYEOF = -1;
-
- /** initial size of the lookahead buffer */
- private static final int ZZ_BUFFERSIZE = 16384;
-
- /** lexical states */
- public static final int YYSTRING1 = 6;
- public static final int YYINITIAL = 0;
- public static final int YYSTYLE = 2;
- public static final int YYVALUE = 4;
- public static final int YYSTRING2 = 8;
-
- /**
- * ZZ_LEXSTATE[l] is the state in the DFA for the lexical state l
- * ZZ_LEXSTATE[l+1] is the state in the DFA for the lexical state l
- * at the beginning of a line
- * l is of the form l = 2*k, k a non negative integer
- */
-// private static final int ZZ_LEXSTATE[] = {
-// 0, 0, 1, 1, 2, 2, 3, 3, 4, 4
-// };
-
- /**
- * Translates characters to character classes
- */
- private static final String ZZ_CMAP_PACKED =
- "\11\0\1\3\1\2\1\0\1\3\1\1\22\0\1\3\1\0\1\22"+
- "\1\14\3\0\1\21\2\0\1\5\1\0\1\12\1\6\1\11\1\4"+
- "\12\10\1\17\1\20\2\0\1\13\2\0\32\7\4\0\1\7\1\0"+
- "\32\7\1\15\1\0\1\16\uff82\0";
-
- /**
- * Translates characters to character classes
- */
- private static final char [] ZZ_CMAP = zzUnpackCMap(ZZ_CMAP_PACKED);
-
- /**
- * Translates DFA states to action switch labels.
- */
- private static final int [] ZZ_ACTION = zzUnpackAction();
-
- private static final String ZZ_ACTION_PACKED_0 =
- "\5\0\1\1\1\2\1\1\1\3\1\1\1\4\1\5"+
- "\1\6\1\7\1\10\1\11\2\12\1\1\1\13\1\14"+
- "\1\15\1\16\1\17\1\20\1\21\1\16\1\22\1\16"+
- "\1\23\4\0";
-
- private static int [] zzUnpackAction() {
- int [] result = new int[34];
- int offset = 0;
- offset = zzUnpackAction(ZZ_ACTION_PACKED_0, offset, result);
- return result;
- }
-
- private static int zzUnpackAction(String packed, int offset, int [] result) {
- int i = 0; /* index in packed string */
- int j = offset; /* index in unpacked array */
- int l = packed.length();
- while (i < l) {
- int count = packed.charAt(i++);
- int value = packed.charAt(i++);
- do result[j++] = value; while (--count > 0);
- }
- return j;
- }
-
-
- /**
- * Translates a state to a row index in the transition table
- */
- private static final int [] ZZ_ROWMAP = zzUnpackRowMap();
-
- private static final String ZZ_ROWMAP_PACKED_0 =
- "\0\0\0\23\0\46\0\71\0\114\0\137\0\162\0\205"+
- "\0\137\0\230\0\253\0\137\0\137\0\137\0\137\0\137"+
- "\0\276\0\137\0\321\0\344\0\137\0\137\0\367\0\137"+
- "\0\137\0\137\0\u010a\0\137\0\u011d\0\137\0\u0130\0\u0143"+
- "\0\u0156\0\u0169";
-
- private static int [] zzUnpackRowMap() {
- int [] result = new int[34];
- int offset = 0;
- offset = zzUnpackRowMap(ZZ_ROWMAP_PACKED_0, offset, result);
- return result;
- }
-
- private static int zzUnpackRowMap(String packed, int offset, int [] result) {
- int i = 0; /* index in packed string */
- int j = offset; /* index in unpacked array */
- int l = packed.length();
- while (i < l) {
- int high = packed.charAt(i++) << 16;
- result[j++] = high | packed.charAt(i++);
- }
- return j;
- }
-
- /**
- * The transition table of the DFA
- */
- private static final int [] ZZ_TRANS = zzUnpackTrans();
-
- private static final String ZZ_TRANS_PACKED_0 =
- "\1\6\3\7\1\10\1\11\1\12\1\13\1\6\1\14"+
- "\1\15\1\16\1\17\1\20\6\6\1\21\2\22\1\10"+
- "\1\6\1\23\1\24\6\6\1\25\1\26\3\6\16\27"+
- "\1\25\1\27\1\30\1\31\1\32\21\33\1\34\1\33"+
- "\22\35\1\36\24\0\3\7\24\0\1\37\24\0\1\13"+
- "\21\0\3\13\14\0\1\22\27\0\1\24\21\0\3\24"+
- "\12\0\16\27\1\0\1\27\3\0\21\33\1\0\1\33"+
- "\22\35\1\0\5\40\1\41\22\40\1\42\15\40\4\0"+
- "\1\22\1\41\15\0\4\40\1\22\1\42\15\40";
-
- private static int [] zzUnpackTrans() {
- int [] result = new int[380];
- int offset = 0;
- offset = zzUnpackTrans(ZZ_TRANS_PACKED_0, offset, result);
- return result;
- }
-
- private static int zzUnpackTrans(String packed, int offset, int [] result) {
- int i = 0; /* index in packed string */
- int j = offset; /* index in unpacked array */
- int l = packed.length();
- while (i < l) {
- int count = packed.charAt(i++);
- int value = packed.charAt(i++);
- value--;
- do result[j++] = value; while (--count > 0);
- }
- return j;
- }
-
- /**
- * ZZ_ATTRIBUTE[aState] contains the attributes of state aState
- */
- private static final int [] ZZ_ATTRIBUTE = zzUnpackAttribute();
-
- private static final String ZZ_ATTRIBUTE_PACKED_0 =
- "\5\0\1\11\2\1\1\11\2\1\5\11\1\1\1\11"+
- "\2\1\2\11\1\1\3\11\1\1\1\11\1\1\1\11"+
- "\4\0";
-
- private static int [] zzUnpackAttribute() {
- int [] result = new int[34];
- int offset = 0;
- offset = zzUnpackAttribute(ZZ_ATTRIBUTE_PACKED_0, offset, result);
- return result;
- }
-
- private static int zzUnpackAttribute(String packed, int offset, int [] result) {
- int i = 0; /* index in packed string */
- int j = offset; /* index in unpacked array */
- int l = packed.length();
- while (i < l) {
- int count = packed.charAt(i++);
- int value = packed.charAt(i++);
- do result[j++] = value; while (--count > 0);
- }
- return j;
- }
-
- /** the input device */
- private java.io.Reader zzReader;
-
- /** the current state of the DFA */
- private int zzState;
-
- /** the current lexical state */
- private int zzLexicalState = YYINITIAL;
-
- /** this buffer contains the current text to be matched and is
- the source of the yytext() string */
- private char zzBuffer[] = new char[ZZ_BUFFERSIZE];
-
- /** the textposition at the last accepting state */
- private int zzMarkedPos;
-
- /** the current text position in the buffer */
- private int zzCurrentPos;
-
- /** startRead marks the beginning of the yytext() string in the buffer */
- private int zzStartRead;
-
- /** endRead marks the last character in the buffer, that has been read
- from input */
- private int zzEndRead;
-
- /** number of newlines encountered up to the start of the matched text */
- private int yyline;
-
- /**
- * the number of characters from the last newline up to the start of the
- * matched text
- */
- private int yycolumn;
-
- /** zzAtEOF == true <=> the scanner is at the EOF */
- private boolean zzAtEOF;
-
- /* user code: */
- static final int EOF = 0;
- static final int IDENT = 1;
- static final int STAR = 2;
- static final int DOT = 3;
- static final int HASH = 4;
- static final int GT = 5;
- static final int COMMA = 6;
- static final int STYLE_BEGIN = 7;
- static final int STYLE_END = 8;
- static final int COLON = 9;
- static final int SEMICOLON = 10;
-
- boolean sawWhitespace;
-
- final StringBuilder sb = new StringBuilder();
- private void append() {
- sb.append(zzBuffer, zzStartRead, zzMarkedPos-zzStartRead);
- }
-
- public void unexpected() throws java.io.IOException {
- throw new java.io.IOException("Unexpected \""+yytext()+"\" at line "+yyline+", column "+yycolumn);
- }
-
- public void expect(int token) throws java.io.IOException {
- if(yylex() != token) unexpected();
- }
-
-
- /**
- * Creates a new scanner
- * There is also a java.io.InputStream version of this constructor.
- *
- * @param in the java.io.Reader to read input from.
- */
- Parser(java.io.Reader in) {
- this.zzReader = in;
- }
-
- /**
- * Unpacks the compressed character translation table.
- *
- * @param packed the packed character translation table
- * @return the unpacked character translation table
- */
- private static char [] zzUnpackCMap(String packed) {
- char [] map = new char[0x10000];
- int i = 0; /* index in packed string */
- int j = 0; /* index in unpacked array */
- while (i < 70) {
- int count = packed.charAt(i++);
- char value = packed.charAt(i++);
- do map[j++] = value; while (--count > 0);
- }
- return map;
- }
-
-
- /**
- * Refills the input buffer.
- *
- * @return false, iff there was new input.
- *
- * @exception java.io.IOException if any I/O-Error occurs
- */
- private boolean zzRefill() throws java.io.IOException {
-
- /* first: make room (if you can) */
- if (zzStartRead > 0) {
- System.arraycopy(zzBuffer, zzStartRead,
- zzBuffer, 0,
- zzEndRead-zzStartRead);
-
- /* translate stored positions */
- zzEndRead-= zzStartRead;
- zzCurrentPos-= zzStartRead;
- zzMarkedPos-= zzStartRead;
- zzStartRead = 0;
- }
-
- /* is the buffer big enough? */
- if (zzCurrentPos >= zzBuffer.length) {
- /* if not: blow it up */
- char newBuffer[] = new char[zzCurrentPos*2];
- System.arraycopy(zzBuffer, 0, newBuffer, 0, zzBuffer.length);
- zzBuffer = newBuffer;
- }
-
- /* finally: fill the buffer with new input */
- int numRead = zzReader.read(zzBuffer, zzEndRead,
- zzBuffer.length-zzEndRead);
-
- if (numRead > 0) {
- zzEndRead+= numRead;
- return false;
- }
- // unlikely but not impossible: read 0 characters, but not at end of stream
- if (numRead == 0) {
- int c = zzReader.read();
- if (c == -1) {
- return true;
- } else {
- zzBuffer[zzEndRead++] = (char) c;
- return false;
- }
- }
-
- // numRead < 0
- return true;
- }
-
-
- /**
- * Enters a new lexical state
- *
- * @param newState the new lexical state
- */
- public final void yybegin(int newState) {
- zzLexicalState = newState;
- }
-
-
- /**
- * Returns the text matched by the current regular expression.
- */
- public final String yytext() {
- return new String( zzBuffer, zzStartRead, zzMarkedPos-zzStartRead );
- }
-
-
- /**
- * Reports an error that occured while scanning.
- *
- * In a wellformed scanner (no or only correct usage of
- * yypushback(int) and a match-all fallback rule) this method
- * will only be called with things that "Can't Possibly Happen".
- * If this method is called, something is seriously wrong
- * (e.g. a JFlex bug producing a faulty scanner etc.).
- *
- * Usual syntax/scanner level error handling should be done
- * in error fallback rules.
- *
- * @param message the errormessage to display
- */
- private void zzScanError(String message) {
- throw new Error(message);
- }
-
-
- /**
- * Resumes scanning until the next regular expression is matched,
- * the end of input is encountered or an I/O-Error occurs.
- *
- * @return the next token
- * @exception java.io.IOException if any I/O-Error occurs
- */
- public int yylex() throws java.io.IOException {
- int zzInput;
- int zzAction;
-
- // cached fields:
- int zzCurrentPosL;
- int zzMarkedPosL;
- int zzEndReadL = zzEndRead;
- char [] zzBufferL = zzBuffer;
- char [] zzCMapL = ZZ_CMAP;
-
- int [] zzTransL = ZZ_TRANS;
- int [] zzRowMapL = ZZ_ROWMAP;
- int [] zzAttrL = ZZ_ATTRIBUTE;
-
- while (true) {
- zzMarkedPosL = zzMarkedPos;
-
- boolean zzR = false;
- for (zzCurrentPosL = zzStartRead; zzCurrentPosL < zzMarkedPosL;
- zzCurrentPosL++) {
- switch (zzBufferL[zzCurrentPosL]) {
- case '\u000B':
- case '\u000C':
- case '\u0085':
- case '\u2028':
- case '\u2029':
- yyline++;
- yycolumn = 0;
- zzR = false;
- break;
- case '\r':
- yyline++;
- yycolumn = 0;
- zzR = true;
- break;
- case '\n':
- if (zzR)
- zzR = false;
- else {
- yyline++;
- yycolumn = 0;
- }
- break;
- default:
- zzR = false;
- yycolumn++;
- }
- }
-
- if (zzR) {
- // peek one character ahead if it is \n (if we have counted one line too much)
- boolean zzPeek;
- if (zzMarkedPosL < zzEndReadL)
- zzPeek = zzBufferL[zzMarkedPosL] == '\n';
- else if (zzAtEOF)
- zzPeek = false;
- else {
- boolean eof = zzRefill();
- zzEndReadL = zzEndRead;
- zzMarkedPosL = zzMarkedPos;
- zzBufferL = zzBuffer;
- if (eof)
- zzPeek = false;
- else
- zzPeek = zzBufferL[zzMarkedPosL] == '\n';
- }
- if (zzPeek) yyline--;
- }
- zzAction = -1;
-
- zzCurrentPosL = zzCurrentPos = zzStartRead = zzMarkedPosL;
-
-// zzState = ZZ_LEXSTATE[zzLexicalState];
- zzState = zzLexicalState / 2;
-
-
- zzForAction: {
- while (true) {
-
- if (zzCurrentPosL < zzEndReadL)
- zzInput = zzBufferL[zzCurrentPosL++];
- else if (zzAtEOF) {
- zzInput = YYEOF;
- break zzForAction;
- }
- else {
- // store back cached positions
- zzCurrentPos = zzCurrentPosL;
- zzMarkedPos = zzMarkedPosL;
- boolean eof = zzRefill();
- // get translated positions and possibly new buffer
- zzCurrentPosL = zzCurrentPos;
- zzMarkedPosL = zzMarkedPos;
- zzBufferL = zzBuffer;
- zzEndReadL = zzEndRead;
- if (eof) {
- zzInput = YYEOF;
- break zzForAction;
- }
- else {
- zzInput = zzBufferL[zzCurrentPosL++];
- }
- }
- int zzNext = zzTransL[ zzRowMapL[zzState] + zzCMapL[zzInput] ];
- if (zzNext == -1) break zzForAction;
- zzState = zzNext;
-
- int zzAttributes = zzAttrL[zzState];
- if ( (zzAttributes & 1) == 1 ) {
- zzAction = zzState;
- zzMarkedPosL = zzCurrentPosL;
- if ( (zzAttributes & 8) == 8 ) break zzForAction;
- }
-
- }
- }
-
- // store back cached position
- zzMarkedPos = zzMarkedPosL;
-
- switch (zzAction < 0 ? zzAction : ZZ_ACTION[zzAction]) {
- case 6:
- { return COMMA;
- }
- case 20: break;
- case 18:
- { yybegin(YYVALUE); sb.append('\'');
- }
- case 21: break;
- case 3:
- { sawWhitespace = false; return STAR;
- }
- case 22: break;
- case 16:
- { yybegin(YYSTRING1); sb.append('\'');
- }
- case 23: break;
- case 17:
- { yybegin(YYSTRING2); sb.append('\"');
- }
- case 24: break;
- case 14:
- { append();
- }
- case 25: break;
- case 4:
- { sawWhitespace = false; return IDENT;
- }
- case 26: break;
- case 19:
- { yybegin(YYVALUE); sb.append('\"');
- }
- case 27: break;
- case 2:
- { sawWhitespace = true;
- }
- case 28: break;
- case 12:
- { yybegin(YYINITIAL); return STYLE_END;
- }
- case 29: break;
- case 15:
- { yybegin(YYSTYLE); return SEMICOLON;
- }
- case 30: break;
- case 13:
- { yybegin(YYVALUE); sb.setLength(0); return COLON;
- }
- case 31: break;
- case 7:
- { return GT;
- }
- case 32: break;
- case 9:
- { yybegin(YYSTYLE); return STYLE_BEGIN;
- }
- case 33: break;
- case 11:
- { return IDENT;
- }
- case 34: break;
- case 1:
- { unexpected();
- }
- case 35: break;
- case 5:
- { return DOT;
- }
- case 36: break;
- case 8:
- { return HASH;
- }
- case 37: break;
- case 10:
- { /* ignore */
- }
- case 38: break;
- default:
- if (zzInput == YYEOF && zzStartRead == zzCurrentPos) {
- zzAtEOF = true;
- {
- return EOF;
- }
- }
- else {
- zzScanError("Error: could not match input");
- }
- }
- }
- }
-
-
-}
+/* The following code was generated by JFlex 1.4.3 on 03.01.12 11:50 */
+
+/*
+ * Copyright (c) 2008-2012, Matthias Mann
+ *
+ * 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 Matthias Mann 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 de.matthiasmann.twl.textarea;
+
+
+/**
+ * This class is a scanner generated by
+ * JFlex 1.4.3
+ * on 03.01.12 11:50 from the specification file
+ * parser.flex
+ */
+class Parser {
+
+ /** This character denotes the end of file */
+ public static final int YYEOF = -1;
+
+ /** initial size of the lookahead buffer */
+ private static final int ZZ_BUFFERSIZE = 16384;
+
+ /** lexical states */
+ public static final int YYSTRING1 = 6;
+ public static final int YYINITIAL = 0;
+ public static final int YYSTYLE = 2;
+ public static final int YYVALUE = 4;
+ public static final int YYSTRING2 = 8;
+
+ /**
+ * ZZ_LEXSTATE[l] is the state in the DFA for the lexical state l
+ * ZZ_LEXSTATE[l+1] is the state in the DFA for the lexical state l
+ * at the beginning of a line
+ * l is of the form l = 2*k, k a non negative integer
+ */
+// private static final int ZZ_LEXSTATE[] = {
+// 0, 0, 1, 1, 2, 2, 3, 3, 4, 4
+// };
+
+ /**
+ * Translates characters to character classes
+ */
+ private static final String ZZ_CMAP_PACKED =
+ "\11\0\1\3\1\2\1\0\1\3\1\1\22\0\1\3\1\0\1\23"+
+ "\1\14\3\0\1\22\2\0\1\5\1\0\1\12\1\6\1\11\1\4"+
+ "\12\10\1\15\1\21\2\0\1\13\1\0\1\16\32\7\4\0\1\7"+
+ "\1\0\32\7\1\17\1\0\1\20\uff82\0";
+
+ /**
+ * Translates characters to character classes
+ */
+ private static final char [] ZZ_CMAP = zzUnpackCMap(ZZ_CMAP_PACKED);
+
+ /**
+ * Translates DFA states to action switch labels.
+ */
+ private static final int [] ZZ_ACTION = zzUnpackAction();
+
+ private static final String ZZ_ACTION_PACKED_0 =
+ "\5\0\1\1\1\2\1\1\1\3\1\1\1\4\1\5"+
+ "\1\6\1\7\1\10\1\11\1\12\1\13\2\14\1\1"+
+ "\1\15\1\16\1\17\1\20\1\21\1\22\1\23\1\20"+
+ "\1\24\1\20\1\25\4\0";
+
+ private static int [] zzUnpackAction() {
+ int [] result = new int[36];
+ zzUnpackAction(ZZ_ACTION_PACKED_0, 0, result);
+ return result;
+ }
+
+ private static int zzUnpackAction(String packed, int offset, int [] result) {
+ int i = 0; /* index in packed string */
+ int j = offset; /* index in unpacked array */
+ int l = packed.length();
+ while (i < l) {
+ int count = packed.charAt(i++);
+ int value = packed.charAt(i++);
+ do result[j++] = value; while (--count > 0);
+ }
+ return j;
+ }
+
+
+ /**
+ * Translates a state to a row index in the transition table
+ */
+ private static final int [] ZZ_ROWMAP = zzUnpackRowMap();
+
+ private static final String ZZ_ROWMAP_PACKED_0 =
+ "\0\0\0\24\0\50\0\74\0\120\0\144\0\170\0\214"+
+ "\0\144\0\240\0\264\0\144\0\144\0\144\0\144\0\144"+
+ "\0\144\0\144\0\310\0\144\0\334\0\360\0\144\0\144"+
+ "\0\u0104\0\144\0\144\0\144\0\u0118\0\144\0\u012c\0\144"+
+ "\0\u0140\0\u0154\0\u0168\0\u017c";
+
+ private static int [] zzUnpackRowMap() {
+ int [] result = new int[36];
+ zzUnpackRowMap(ZZ_ROWMAP_PACKED_0, 0, result);
+ return result;
+ }
+
+ private static int zzUnpackRowMap(String packed, int offset, int [] result) {
+ int i = 0; /* index in packed string */
+ int j = offset; /* index in unpacked array */
+ int l = packed.length();
+ while (i < l) {
+ int high = packed.charAt(i++) << 16;
+ result[j++] = high | packed.charAt(i++);
+ }
+ return j;
+ }
+
+ /**
+ * The transition table of the DFA
+ */
+ private static final int [] ZZ_TRANS = zzUnpackTrans();
+
+ private static final String ZZ_TRANS_PACKED_0 =
+ "\1\6\3\7\1\10\1\11\1\12\1\13\1\6\1\14"+
+ "\1\15\1\16\1\17\1\20\1\21\1\22\5\6\1\23"+
+ "\2\24\1\10\1\6\1\25\1\26\5\6\1\27\2\6"+
+ "\1\30\3\6\20\31\1\30\1\32\1\33\1\34\22\35"+
+ "\1\36\1\35\23\37\1\40\25\0\3\7\25\0\1\41"+
+ "\25\0\1\13\22\0\3\13\15\0\1\24\30\0\1\26"+
+ "\22\0\3\26\13\0\20\31\4\0\22\35\1\0\1\35"+
+ "\23\37\1\0\5\42\1\43\23\42\1\44\16\42\4\0"+
+ "\1\24\1\43\16\0\4\42\1\24\1\44\16\42";
+
+ private static int [] zzUnpackTrans() {
+ int [] result = new int[400];
+ zzUnpackTrans(ZZ_TRANS_PACKED_0, 0, result);
+ return result;
+ }
+
+ private static int zzUnpackTrans(String packed, int offset, int [] result) {
+ int i = 0; /* index in packed string */
+ int j = offset; /* index in unpacked array */
+ int l = packed.length();
+ while (i < l) {
+ int count = packed.charAt(i++);
+ int value = packed.charAt(i++);
+ value--;
+ do result[j++] = value; while (--count > 0);
+ }
+ return j;
+ }
+
+ /**
+ * ZZ_ATTRIBUTE[aState] contains the attributes of state aState
+ */
+ private static final int [] ZZ_ATTRIBUTE = zzUnpackAttribute();
+
+ private static final String ZZ_ATTRIBUTE_PACKED_0 =
+ "\5\0\1\11\2\1\1\11\2\1\7\11\1\1\1\11"+
+ "\2\1\2\11\1\1\3\11\1\1\1\11\1\1\1\11"+
+ "\4\0";
+
+ private static int [] zzUnpackAttribute() {
+ int [] result = new int[36];
+ zzUnpackAttribute(ZZ_ATTRIBUTE_PACKED_0, 0, result);
+ return result;
+ }
+
+ private static int zzUnpackAttribute(String packed, int offset, int [] result) {
+ int i = 0; /* index in packed string */
+ int j = offset; /* index in unpacked array */
+ int l = packed.length();
+ while (i < l) {
+ int count = packed.charAt(i++);
+ int value = packed.charAt(i++);
+ do result[j++] = value; while (--count > 0);
+ }
+ return j;
+ }
+
+ /** the input device */
+ private java.io.Reader zzReader;
+
+ /** the current state of the DFA */
+ private int zzState;
+
+ /** the current lexical state */
+ private int zzLexicalState = YYINITIAL;
+
+ /** this buffer contains the current text to be matched and is
+ the source of the yytext() string */
+ private char zzBuffer[] = new char[ZZ_BUFFERSIZE];
+
+ /** the textposition at the last accepting state */
+ private int zzMarkedPos;
+
+ /** the current text position in the buffer */
+ private int zzCurrentPos;
+
+ /** startRead marks the beginning of the yytext() string in the buffer */
+ private int zzStartRead;
+
+ /** endRead marks the last character in the buffer, that has been read
+ from input */
+ private int zzEndRead;
+
+ /** number of newlines encountered up to the start of the matched text */
+ private int yyline;
+
+ /**
+ * the number of characters from the last newline up to the start of the
+ * matched text
+ */
+ private int yycolumn;
+
+ /** zzAtEOF == true <=> the scanner is at the EOF */
+ private boolean zzAtEOF;
+
+ /* user code: */
+ static final int EOF = 0;
+ static final int IDENT = 1;
+ static final int STAR = 2;
+ static final int DOT = 3;
+ static final int HASH = 4;
+ static final int GT = 5;
+ static final int COMMA = 6;
+ static final int STYLE_BEGIN = 7;
+ static final int STYLE_END = 8;
+ static final int COLON = 9;
+ static final int SEMICOLON = 10;
+ static final int ATRULE = 11;
+
+ boolean sawWhitespace;
+
+ final StringBuilder sb = new StringBuilder();
+ private void append() {
+ sb.append(zzBuffer, zzStartRead, zzMarkedPos-zzStartRead);
+ }
+
+ public void unexpected() throws java.io.IOException {
+ throw new java.io.IOException("Unexpected \""+yytext()+"\" at line "+yyline+", column "+yycolumn);
+ }
+
+ public void expect(int token) throws java.io.IOException {
+ if(yylex() != token) unexpected();
+ }
+
+
+ /**
+ * Creates a new scanner
+ * There is also a java.io.InputStream version of this constructor.
+ *
+ * @param in the java.io.Reader to read input from.
+ */
+ Parser(java.io.Reader in) {
+ this.zzReader = in;
+ }
+
+ /**
+ * Unpacks the compressed character translation table.
+ *
+ * @param packed the packed character translation table
+ * @return the unpacked character translation table
+ */
+ private static char [] zzUnpackCMap(String packed) {
+ char [] map = new char[0x10000];
+ int i = 0; /* index in packed string */
+ int j = 0; /* index in unpacked array */
+ while (i < 72) {
+ int count = packed.charAt(i++);
+ char value = packed.charAt(i++);
+ do map[j++] = value; while (--count > 0);
+ }
+ return map;
+ }
+
+
+ /**
+ * Refills the input buffer.
+ *
+ * @return false, iff there was new input.
+ *
+ * @exception java.io.IOException if any I/O-Error occurs
+ */
+ private boolean zzRefill() throws java.io.IOException {
+
+ /* first: make room (if you can) */
+ if (zzStartRead > 0) {
+ System.arraycopy(zzBuffer, zzStartRead,
+ zzBuffer, 0,
+ zzEndRead-zzStartRead);
+
+ /* translate stored positions */
+ zzEndRead-= zzStartRead;
+ zzCurrentPos-= zzStartRead;
+ zzMarkedPos-= zzStartRead;
+ zzStartRead = 0;
+ }
+
+ /* is the buffer big enough? */
+ if (zzCurrentPos >= zzBuffer.length) {
+ /* if not: blow it up */
+ char newBuffer[] = new char[zzCurrentPos*2];
+ System.arraycopy(zzBuffer, 0, newBuffer, 0, zzBuffer.length);
+ zzBuffer = newBuffer;
+ }
+
+ /* finally: fill the buffer with new input */
+ int numRead = zzReader.read(zzBuffer, zzEndRead,
+ zzBuffer.length-zzEndRead);
+
+ if (numRead > 0) {
+ zzEndRead+= numRead;
+ return false;
+ }
+ // unlikely but not impossible: read 0 characters, but not at end of stream
+ if (numRead == 0) {
+ int c = zzReader.read();
+ if (c == -1) {
+ return true;
+ } else {
+ zzBuffer[zzEndRead++] = (char) c;
+ return false;
+ }
+ }
+
+ // numRead < 0
+ return true;
+ }
+
+
+ /**
+ * Enters a new lexical state
+ *
+ * @param newState the new lexical state
+ */
+ public final void yybegin(int newState) {
+ zzLexicalState = newState;
+ }
+
+
+ /**
+ * Returns the text matched by the current regular expression.
+ */
+ public final String yytext() {
+ return new String( zzBuffer, zzStartRead, zzMarkedPos-zzStartRead );
+ }
+
+
+ /**
+ * Reports an error that occured while scanning.
+ *
+ * In a wellformed scanner (no or only correct usage of
+ * yypushback(int) and a match-all fallback rule) this method
+ * will only be called with things that "Can't Possibly Happen".
+ * If this method is called, something is seriously wrong
+ * (e.g. a JFlex bug producing a faulty scanner etc.).
+ *
+ * Usual syntax/scanner level error handling should be done
+ * in error fallback rules.
+ *
+ * @param message the errormessage to display
+ */
+ private void zzScanError(String message) {
+ throw new Error(message);
+ }
+
+
+ /**
+ * Resumes scanning until the next regular expression is matched,
+ * the end of input is encountered or an I/O-Error occurs.
+ *
+ * @return the next token
+ * @exception java.io.IOException if any I/O-Error occurs
+ */
+ public int yylex() throws java.io.IOException {
+ int zzInput;
+ int zzAction;
+
+ // cached fields:
+ int zzCurrentPosL;
+ int zzMarkedPosL;
+ int zzEndReadL = zzEndRead;
+ char [] zzBufferL = zzBuffer;
+ char [] zzCMapL = ZZ_CMAP;
+
+ int [] zzTransL = ZZ_TRANS;
+ int [] zzRowMapL = ZZ_ROWMAP;
+ int [] zzAttrL = ZZ_ATTRIBUTE;
+
+ while (true) {
+ zzMarkedPosL = zzMarkedPos;
+
+ boolean zzR = false;
+ for (zzCurrentPosL = zzStartRead; zzCurrentPosL < zzMarkedPosL;
+ zzCurrentPosL++) {
+ switch (zzBufferL[zzCurrentPosL]) {
+ case '\u000B':
+ case '\u000C':
+ case '\u0085':
+ case '\u2028':
+ case '\u2029':
+ yyline++;
+ yycolumn = 0;
+ zzR = false;
+ break;
+ case '\r':
+ yyline++;
+ yycolumn = 0;
+ zzR = true;
+ break;
+ case '\n':
+ if (zzR)
+ zzR = false;
+ else {
+ yyline++;
+ yycolumn = 0;
+ }
+ break;
+ default:
+ zzR = false;
+ yycolumn++;
+ }
+ }
+
+ if (zzR) {
+ // peek one character ahead if it is \n (if we have counted one line too much)
+ boolean zzPeek;
+ if (zzMarkedPosL < zzEndReadL)
+ zzPeek = zzBufferL[zzMarkedPosL] == '\n';
+ else if (zzAtEOF)
+ zzPeek = false;
+ else {
+ boolean eof = zzRefill();
+ zzEndReadL = zzEndRead;
+ zzMarkedPosL = zzMarkedPos;
+ zzBufferL = zzBuffer;
+ if (eof)
+ zzPeek = false;
+ else
+ zzPeek = zzBufferL[zzMarkedPosL] == '\n';
+ }
+ if (zzPeek) yyline--;
+ }
+ zzAction = -1;
+
+ zzCurrentPosL = zzCurrentPos = zzStartRead = zzMarkedPosL;
+
+// zzState = ZZ_LEXSTATE[zzLexicalState];
+ zzState = zzLexicalState / 2;
+
+
+ zzForAction: {
+ while (true) {
+
+ if (zzCurrentPosL < zzEndReadL)
+ zzInput = zzBufferL[zzCurrentPosL++];
+ else if (zzAtEOF) {
+ zzInput = YYEOF;
+ break zzForAction;
+ }
+ else {
+ // store back cached positions
+ zzCurrentPos = zzCurrentPosL;
+ zzMarkedPos = zzMarkedPosL;
+ boolean eof = zzRefill();
+ // get translated positions and possibly new buffer
+ zzCurrentPosL = zzCurrentPos;
+ zzMarkedPosL = zzMarkedPos;
+ zzBufferL = zzBuffer;
+ zzEndReadL = zzEndRead;
+ if (eof) {
+ zzInput = YYEOF;
+ break zzForAction;
+ }
+ else {
+ zzInput = zzBufferL[zzCurrentPosL++];
+ }
+ }
+ int zzNext = zzTransL[ zzRowMapL[zzState] + zzCMapL[zzInput] ];
+ if (zzNext == -1) break zzForAction;
+ zzState = zzNext;
+
+ int zzAttributes = zzAttrL[zzState];
+ if ( (zzAttributes & 1) == 1 ) {
+ zzAction = zzState;
+ zzMarkedPosL = zzCurrentPosL;
+ if ( (zzAttributes & 8) == 8 ) break zzForAction;
+ }
+
+ }
+ }
+
+ // store back cached position
+ zzMarkedPos = zzMarkedPosL;
+
+ switch (zzAction < 0 ? zzAction : ZZ_ACTION[zzAction]) {
+ case 6:
+ { return COMMA;
+ }
+ case 22: break;
+ case 20:
+ { yybegin(YYVALUE); sb.append('\'');
+ }
+ case 23: break;
+ case 10:
+ { return ATRULE;
+ }
+ case 24: break;
+ case 3:
+ { sawWhitespace = false; return STAR;
+ }
+ case 25: break;
+ case 18:
+ { yybegin(YYSTRING1); sb.append('\'');
+ }
+ case 26: break;
+ case 19:
+ { yybegin(YYSTRING2); sb.append('\"');
+ }
+ case 27: break;
+ case 16:
+ { append();
+ }
+ case 28: break;
+ case 4:
+ { sawWhitespace = false; return IDENT;
+ }
+ case 29: break;
+ case 21:
+ { yybegin(YYVALUE); sb.append('\"');
+ }
+ case 30: break;
+ case 9:
+ { return COLON;
+ }
+ case 31: break;
+ case 2:
+ { sawWhitespace = true;
+ }
+ case 32: break;
+ case 15:
+ { yybegin(YYINITIAL); return STYLE_END;
+ }
+ case 33: break;
+ case 17:
+ { yybegin(YYSTYLE); return SEMICOLON;
+ }
+ case 34: break;
+ case 14:
+ { yybegin(YYVALUE); sb.setLength(0); return COLON;
+ }
+ case 35: break;
+ case 7:
+ { return GT;
+ }
+ case 36: break;
+ case 11:
+ { yybegin(YYSTYLE); return STYLE_BEGIN;
+ }
+ case 37: break;
+ case 13:
+ { return IDENT;
+ }
+ case 38: break;
+ case 1:
+ { unexpected();
+ }
+ case 39: break;
+ case 5:
+ { return DOT;
+ }
+ case 40: break;
+ case 8:
+ { return HASH;
+ }
+ case 41: break;
+ case 12:
+ { /* ignore */
+ }
+ case 42: break;
+ default:
+ if (zzInput == YYEOF && zzStartRead == zzCurrentPos) {
+ zzAtEOF = true;
+ {
+ return EOF;
+ }
+ }
+ else {
+ zzScanError("Error: could not match input");
+ }
+ }
+ }
+ }
+
+
+}
diff --git a/twl/src/de/matthiasmann/twl/textarea/SimpleTextAreaModel.java b/twl/src/de/matthiasmann/twl/textarea/SimpleTextAreaModel.java
index f480a94..fe7bbde 100644
--- a/twl/src/de/matthiasmann/twl/textarea/SimpleTextAreaModel.java
+++ b/twl/src/de/matthiasmann/twl/textarea/SimpleTextAreaModel.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2008-2010, Matthias Mann
+ * Copyright (c) 2008-2012, Matthias Mann
*
* All rights reserved.
*
@@ -35,17 +35,21 @@
/**
* A simple text area model which represents the complete text as a single
- * paragraph without any styles.
+ * paragraph.
+ *
+ *
The initial style is an empty style - see {@link Style#Style() }.
+ * It can be changed before setting the text.
*
* @author Matthias Mann
+ * @see #setStyle(de.matthiasmann.twl.textarea.Style)
*/
public class SimpleTextAreaModel extends HasCallback implements TextAreaModel {
- private static final Style EMPTY_STYLE = new Style();
-
+ private Style style;
private Element element;
public SimpleTextAreaModel() {
+ style = new Style();
}
/**
@@ -57,15 +61,39 @@ public SimpleTextAreaModel() {
*/
@SuppressWarnings("OverridableMethodCallInConstructor")
public SimpleTextAreaModel(String text) {
+ this();
setText(text);
}
+ /**
+ * Returns the style used for the next call to {@link #setText(java.lang.String, boolean) }
+ * @return the style
+ */
+ public Style getStyle() {
+ return style;
+ }
+
+ /**
+ * Sets the style used for the next call to {@link #setText(java.lang.String, boolean) }.
+ * It does not affect the currently set text.
+ *
+ * @param style the style
+ * @throws NullPointerException when style is {@code null}
+ */
+ public void setStyle(Style style) {
+ if(style == null) {
+ throw new NullPointerException("style");
+ }
+ this.style = style;
+ }
+
/**
* Sets the text for this SimpleTextAreaModel as pre-formatted text.
* Use {@code '\n'} to create line breaks.
*
* This is equivalent to calling {@code setText(text, true);}
* @param text the text (interpreted as pre-formatted)
+ * @see #setText(java.lang.String, boolean)
*/
public void setText(String text) {
setText(text, true);
@@ -75,18 +103,18 @@ public void setText(String text) {
* Sets the text for this SimpleTextAreaModel.
* Use {@code '\n'} to create line breaks.
*
+ *
The {@code preformatted} will set the white space attribute as follows: