diff --git a/build.gradle.kts b/build.gradle.kts index 0b7be1d..012ba5b 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,3 +1,6 @@ +import org.gradle.api.tasks.bundling.Jar +import org.gradle.api.tasks.bundling.Zip + plugins { id("java") } @@ -14,4 +17,15 @@ repositories { dependencies { implementation("org.gotti.wurmunlimited:server-modlauncher:0.46") -} \ No newline at end of file +} + +tasks.register("dist") { + into("mods") { + into(project.name) { + from(tasks.named("jar")) + } + from(fileTree("mods") { include("*") }) + } + + archiveFileName.set("${project.name}.zip") +} diff --git a/mods/ShopMod.properties b/mods/ShopMod.properties index f1e0f9b..e65e2e8 100644 --- a/mods/ShopMod.properties +++ b/mods/ShopMod.properties @@ -2,6 +2,18 @@ classname=mod.treestar.shopmod.ShopMod classpath=ShopMod.jar sharedClassLoader=true -# Activates the json item provider and loads items from the given configuration -jsonItemProvider=true -jsonItemProviderItems=mods/shop_items.json \ No newline at end of file +# Display name shown in the shop window +shopName=Server Shop + +# Allow opening the shop from settlement tokens +enableTokenAccess=true + +# Allow opening the shop from mailboxes +enableMailboxAccess=false + +# JSON config files for categories and items +categoryJsonPath=mods/shop_categories.json +itemJsonPath=mods/shop_items.json + +# java.util.logging level for this mod (e.g., INFO, FINE) +logLevel=INFO diff --git a/settings.gradle.kts b/settings.gradle.kts index 7310848..a14c0b3 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -1 +1 @@ -rootProject.name = "SilverShop" \ No newline at end of file +rootProject.name = "ShopMod" \ No newline at end of file diff --git a/src/main/java/com/wurmonline/server/questions/ShopQuestion.java b/src/main/java/com/wurmonline/server/questions/ShopQuestion.java index 290035f..dad518b 100644 --- a/src/main/java/com/wurmonline/server/questions/ShopQuestion.java +++ b/src/main/java/com/wurmonline/server/questions/ShopQuestion.java @@ -6,13 +6,17 @@ import com.wurmonline.server.players.Player; import mod.treestar.shopmod.ShopService; import mod.treestar.shopmod.datamodels.ShopCategory; import mod.treestar.shopmod.datamodels.ShopItem; +import mod.treestar.shopmod.util.BmlForm; import java.util.List; import java.util.Properties; +import java.util.logging.Level; +import java.util.logging.Logger; import java.util.stream.Collectors; public class ShopQuestion extends Question { private static final int SHOP_QUESTION_ID = 90001; + private static final Logger logger = Logger.getLogger(ShopQuestion.class.getName()); private ShopService shopService; private int selectedCategoryId = -1; @@ -28,49 +32,66 @@ public class ShopQuestion extends Question { @Override public void answer(Properties properties) { - super.answer(properties); + logger.log(Level.INFO, "ShopQuestion.answer: received properties {0}", properties); List categories = shopService.getCategories(); - - // Update category selection if present String categoryIndex = properties.getProperty("category"); if (categoryIndex != null && !categories.isEmpty()) { try { int idx = Integer.parseInt(categoryIndex); if (idx >= 0 && idx < categories.size()) { selectedCategoryId = categories.get(idx).getId(); + logger.log(Level.INFO, "ShopQuestion.answer: category set to index {0}, id {1}", new Object[] { idx, selectedCategoryId }); } - } catch (NumberFormatException ignored) { } + } catch (NumberFormatException ignored) { + logger.log(Level.INFO, "ShopQuestion.answer: invalid category index {0}", categoryIndex); + } } - // If purchase not requested, refresh UI with selected category - if (!properties.containsKey("submit")) { + String buyKey = properties.stringPropertyNames().stream() + .filter(k -> k.startsWith("buy_")) + .findFirst() + .orElse(null); + + if (properties.containsKey("close")) { + logger.log(Level.INFO, "ShopQuestion.answer: close requested"); + return; // user closed the window + } + + if (properties.containsKey("refresh")) { + logger.log(Level.INFO, "ShopQuestion.answer: refresh requested"); sendQuestion(); return; } - String itemIdStr = properties.getProperty("itemid"); - if (itemIdStr == null) { - getResponder().getCommunicator().sendNormalServerMessage("No item selected."); + if (buyKey == null) { // category change or refresh + logger.log(Level.INFO, "ShopQuestion.answer: no buy key present, refreshing UI"); + sendQuestion(); return; } - int itemId; + try { - itemId = Integer.parseInt(itemIdStr); + int itemId = Integer.parseInt(buyKey.substring("buy_".length())); + logger.log(Level.INFO, "ShopQuestion.answer: attempting purchase itemId={0}", itemId); + ShopService.PurchaseResult result = shopService.purchaseItem((Player) getResponder(), itemId); + if (result.isSuccess()) { + logger.log(Level.INFO, "ShopQuestion.answer: purchase success itemId={0}", itemId); + getResponder().getCommunicator().sendSafeServerMessage(result.getMessage()); + } else { + logger.log(Level.INFO, "ShopQuestion.answer: purchase failed itemId={0}, message={1}", new Object[] { itemId, result.getMessage() }); + getResponder().getCommunicator().sendAlertServerMessage(result.getMessage()); + } } catch (NumberFormatException e) { + logger.log(Level.INFO, "ShopQuestion.answer: invalid buy key {0}", buyKey); getResponder().getCommunicator().sendNormalServerMessage("Invalid item selected."); - return; - } - ShopService.PurchaseResult result = shopService.purchaseItem((Player)getResponder(), itemId); - if (result.isSuccess()) { - getResponder().getCommunicator().sendSafeServerMessage(result.getMessage()); - } else { - getResponder().getCommunicator().sendNormalServerMessage(result.getMessage()); } } @Override public void sendQuestion() { List categories = shopService.getCategories(); + categories = categories.stream() + .filter(c -> shopService.getItems().stream().anyMatch(i -> i.getCategoryId() == c.getId())) + .collect(Collectors.toList()); if (categories.isEmpty()) { getResponder().getCommunicator().sendNormalServerMessage("No shop categories are available."); return; @@ -78,13 +99,6 @@ public class ShopQuestion extends Question { if (selectedCategoryId == -1 || categories.stream().noneMatch(c -> c.getId() == selectedCategoryId)) { selectedCategoryId = categories.get(0).getId(); } - int defaultCategoryIndex = 0; - for (int i = 0; i < categories.size(); i++) { - if (categories.get(i).getId() == selectedCategoryId) { - defaultCategoryIndex = i; - break; - } - } List items = shopService.getItems().stream() .filter(i -> i.getCategoryId() == selectedCategoryId) @@ -93,24 +107,42 @@ public class ShopQuestion extends Question { getResponder().getCommunicator().sendNormalServerMessage("No shop items are available."); return; } - StringBuilder bml = new StringBuilder(); - bml.append("border{center{heading{text=\"").append(escape(title)).append("\"}}"); + BmlForm form = new BmlForm(title); + form.addHidden("id", String.valueOf(this.id)); + // Category selector with refresh button if (categories.size() > 1) { - bml.append("harray{label{text=\"Category\"};dropdown{id=\"category\";options=\"") - .append(categories.stream().map(c -> escape(c.getName())).collect(Collectors.joining(","))) - .append("\";default=\"").append(defaultCategoryIndex).append("\"}}"); + String options = categories.stream().map(c -> escape(c.getName())).collect(Collectors.joining(",")); + int defaultIndex = 0; + for (int i = 0; i < categories.size(); i++) { + if (categories.get(i).getId() == selectedCategoryId) { + defaultIndex = i; + break; + } + } + form.beginHorizontalFlow(); + form.addLabel("Category"); + form.addRaw("dropdown{id=\"" + escape("category") + "\";options=\"" + options + "\";default=\"" + defaultIndex + "\"}"); + form.addButton("Show", "refresh"); + form.endHorizontalFlow(); } - bml.append("center{scroll{table{rows=\""); + + // Items list (one row per item) for (ShopItem item : items) { - bml.append("label{text=\"").append(escape(item.getName())).append("\"};"); - bml.append("label{text=\"").append(escape(item.getDescription())).append("\"};"); - bml.append("label{text=\"").append(escape(item.getPriceDisplay())).append("\"};"); - bml.append("radio{group=\"itemid\";id=\"").append(item.getId()).append("\"};"); + form.beginHorizontalFlow(); + form.addLabel(escape(item.getName())); + form.addLabel(escape(item.getDescription())); + form.addLabel(escape(item.getPriceDisplay())); + form.addButton("+1", "buy_" + item.getId()); + form.endHorizontalFlow(); } - bml.append("\"}}}"); - bml.append("harray{button{id=\"submit\";text=\"Purchase\"};button{id=\"cancel\";text=\"Cancel\"}}"); - bml.append("}"); - getResponder().getCommunicator().sendBml(400, 400, true, true, bml.toString(), 200, 200, 200, title); + + form.beginHorizontalFlow(); + form.addButton("Close", "close"); + form.endHorizontalFlow(); + + String bml = form.toString(); + logger.log(Level.INFO, "ShopQuestion BML: {0}", bml); + getResponder().getCommunicator().sendBml(400, 400, true, true, bml, 200, 200, 200, title); } public ShopService getShopService() { @@ -122,6 +154,9 @@ public class ShopQuestion extends Question { } private String escape(String value) { - return value == null ? "" : value.replace("\"", "''"); + if (value == null) { + return ""; + } + return value.replace("\"", "''").replace("'", "''"); } } diff --git a/src/main/java/mod/treestar/shopmod/ShopMod.java b/src/main/java/mod/treestar/shopmod/ShopMod.java index f99c773..fe6cc45 100644 --- a/src/main/java/mod/treestar/shopmod/ShopMod.java +++ b/src/main/java/mod/treestar/shopmod/ShopMod.java @@ -5,6 +5,7 @@ import mod.treestar.shopmod.itemprovider.JsonShopItemProvider; import mod.treestar.shopmod.ShopOpenAction; import org.gotti.wurmunlimited.modloader.interfaces.Configurable; import org.gotti.wurmunlimited.modloader.interfaces.Initable; +import org.gotti.wurmunlimited.modloader.interfaces.ServerStartedListener; import org.gotti.wurmunlimited.modloader.interfaces.WurmServerMod; import org.gotti.wurmunlimited.modsupport.actions.ModActions; @@ -12,7 +13,7 @@ import java.util.Properties; import java.util.logging.Level; import java.util.logging.Logger; -public class ShopMod implements WurmServerMod, Initable, Configurable { +public class ShopMod implements WurmServerMod, Initable, Configurable, ServerStartedListener { private static final Logger logger = Logger.getLogger(ShopMod.class.getName()); private String shopName = "Server Shop"; @@ -21,6 +22,7 @@ public class ShopMod implements WurmServerMod, Initable, Configurable { private String categoryJsonPath = "mods/shop/categories.json"; private String itemJsonPath = "mods/shop/items.json"; private Level logLevel = Level.INFO; + private ShopService shopService; @Override public void configure(Properties properties) { @@ -37,11 +39,13 @@ public class ShopMod implements WurmServerMod, Initable, Configurable { public void init() { ModActions.init(); - ShopService shopService = ShopService.getInstance(); - + shopService = ShopService.getInstance(); shopService.registerCategoryProvider(new JsonShopCategoryProvider(categoryJsonPath)); shopService.registerItemProvider(new JsonShopItemProvider(itemJsonPath)); + } + @Override + public void onServerStarted() { new ShopOpenAction(shopService, shopName, enableTokenAccess, enableMailboxAccess); logger.log(Level.INFO, String.format("Initialized shop '%s' (token access: %s, mailbox access: %s)", shopName, enableTokenAccess, enableMailboxAccess)); diff --git a/src/main/java/mod/treestar/shopmod/ShopOpenAction.java b/src/main/java/mod/treestar/shopmod/ShopOpenAction.java index d25f84f..8e6294c 100644 --- a/src/main/java/mod/treestar/shopmod/ShopOpenAction.java +++ b/src/main/java/mod/treestar/shopmod/ShopOpenAction.java @@ -66,6 +66,11 @@ public class ShopOpenAction implements ActionPerformer, BehaviourProvider { return false; // not supported } + @Override + public boolean action(Action action, Creature performer, Item source, Item target, short actionId, float counter) { + return action(action, performer, target, actionId, counter); + } + @Override public List getBehavioursFor(Creature performer, Item target) { if (isValidTarget(target)) { @@ -74,6 +79,11 @@ public class ShopOpenAction implements ActionPerformer, BehaviourProvider { return null; } + @Override + public List getBehavioursFor(Creature performer, Item source, Item target) { + return getBehavioursFor(performer, target); + } + @Override public List getBehavioursFor(Creature performer, Creature target) { return null; diff --git a/src/main/java/mod/treestar/shopmod/ShopService.java b/src/main/java/mod/treestar/shopmod/ShopService.java index 825266f..2afb441 100644 --- a/src/main/java/mod/treestar/shopmod/ShopService.java +++ b/src/main/java/mod/treestar/shopmod/ShopService.java @@ -32,9 +32,13 @@ public class ShopService { public List getCategories() { List categories = new ArrayList<>(); for (ShopCategoryProvider provider : categoryProviders) { - List provided = provider.getCategories(); - if (provided != null) { - categories.addAll(provided); + try { + List provided = provider.getCategories(); + if (provided != null) { + categories.addAll(provided); + } + } catch (Exception e) { + logger.log(Level.WARNING, "Category provider threw an exception", e); } } return categories; @@ -43,9 +47,13 @@ public class ShopService { public List getItems() { List items = new ArrayList<>(); for (ShopItemProvider provider : itemProviders) { - List provided = provider.getItems(); - if (provided != null) { - items.addAll(provided); + try { + List provided = provider.getItems(); + if (provided != null) { + items.addAll(provided); + } + } catch (Exception e) { + logger.log(Level.WARNING, "Item provider threw an exception", e); } } return items; @@ -66,12 +74,17 @@ public class ShopService { if (item.getPurchaseHandler() == null) { return PurchaseResult.failure("Item has no purchase handler configured."); } - if (!item.getCurrency().canPlayerAfford(player)) { - return PurchaseResult.failure("You cannot afford this item."); - } - boolean charged = item.getCurrency().chargePlayer(player); - if (!charged) { - return PurchaseResult.failure("Charging failed; purchase canceled."); + try { + if (!item.getCurrency().canPlayerAfford(player)) { + return PurchaseResult.failure("You cannot afford this item."); + } + boolean charged = item.getCurrency().chargePlayer(player); + if (!charged) { + return PurchaseResult.failure("Charging failed; purchase canceled."); + } + } catch (Exception e) { + logger.log(Level.WARNING, "Currency check/charge failed for item " + itemId + " for player " + player.getName(), e); + return PurchaseResult.failure("Payment failed; purchase canceled."); } try { item.getPurchaseHandler().onPurchase(player); diff --git a/src/main/java/mod/treestar/shopmod/categoryprovider/JsonShopCategoryProvider.java b/src/main/java/mod/treestar/shopmod/categoryprovider/JsonShopCategoryProvider.java index d2fc2a4..41a6bab 100644 --- a/src/main/java/mod/treestar/shopmod/categoryprovider/JsonShopCategoryProvider.java +++ b/src/main/java/mod/treestar/shopmod/categoryprovider/JsonShopCategoryProvider.java @@ -50,6 +50,8 @@ public class JsonShopCategoryProvider implements ShopCategoryProvider { JSONArray typeArray = new JSONArray(tokenizer); cachedCategories = parseCategories(typeArray); lastModified = file.lastModified(); + } catch (Exception e) { + logger.log(Level.WARNING, "Failed to parse categories JSON; keeping previous categories", e); } } diff --git a/src/main/java/mod/treestar/shopmod/itemprovider/JsonShopItemProvider.java b/src/main/java/mod/treestar/shopmod/itemprovider/JsonShopItemProvider.java index 3f0ed10..c570314 100644 --- a/src/main/java/mod/treestar/shopmod/itemprovider/JsonShopItemProvider.java +++ b/src/main/java/mod/treestar/shopmod/itemprovider/JsonShopItemProvider.java @@ -60,6 +60,8 @@ public class JsonShopItemProvider implements ShopItemProvider { JSONArray typeArray = new JSONArray(tokenizer); cachedItems = parseItems(typeArray); lastModified = file.lastModified(); + } catch (Exception e) { + logger.log(Level.WARNING, "Failed to parse items JSON; keeping previous items", e); } } @@ -95,9 +97,9 @@ public class JsonShopItemProvider implements ShopItemProvider { logger.log(Level.WARNING, "Item at index " + index + " missing currency; item will be unusable"); return null; } - String type = currencyObj.optString("type", ""); + String type = currencyObj.optString("type", "").toLowerCase(Locale.ROOT); switch (type) { - case "WurmBankCurrency": + case "wurmbankcurrency": long ironAmount = currencyObj.optLong("ironAmount", currencyObj.optLong("priceIron", 0L)); return new WurmBankCurrency(ironAmount); default: @@ -111,23 +113,23 @@ public class JsonShopItemProvider implements ShopItemProvider { logger.log(Level.WARNING, "Item at index " + index + " missing handler; item will be unusable"); return null; } - String type = handlerObj.optString("type", ""); + String type = handlerObj.optString("type", "").toLowerCase(Locale.ROOT); switch (type) { - case "ShopWurmItemPurchaseEffect": - int templateId = handlerObj.optInt("itemTemplateId", -1); - if (templateId <= 0) { - logger.log(Level.WARNING, "Handler for item at index " + index + " missing valid itemTemplateId"); - return null; - } - ShopWurmItemPurchaseEffect effect = new ShopWurmItemPurchaseEffect(); - effect.setItemTemplateId(templateId); - effect.setQl((float) handlerObj.optDouble("ql", 50)); - effect.setRandomQl(handlerObj.optBoolean("randomQl", false)); - effect.setRarity((byte) handlerObj.optInt("rarity", 0)); - return effect; - default: - logger.log(Level.WARNING, "Unknown handler type '" + type + "' for item at index " + index); + case "shopwurmitempurchaseeffect": + int templateId = handlerObj.optInt("itemTemplateId", -1); + if (templateId <= 0) { + logger.log(Level.WARNING, "Handler for item at index " + index + " missing valid itemTemplateId"); return null; + } + ShopWurmItemPurchaseEffect effect = new ShopWurmItemPurchaseEffect(); + effect.setItemTemplateId(templateId); + effect.setQl((float) handlerObj.optDouble("ql", 50)); + effect.setRandomQl(handlerObj.optBoolean("randomQl", false)); + effect.setRarity((byte) handlerObj.optInt("rarity", 0)); + return effect; + default: + logger.log(Level.WARNING, "Unknown handler type '" + type + "' for item at index " + index); + return null; } } } diff --git a/src/main/java/mod/treestar/shopmod/util/BmlForm.java b/src/main/java/mod/treestar/shopmod/util/BmlForm.java new file mode 100644 index 0000000..5477b45 --- /dev/null +++ b/src/main/java/mod/treestar/shopmod/util/BmlForm.java @@ -0,0 +1,226 @@ +package mod.treestar.shopmod.util; + +import java.util.logging.Level; +import java.util.logging.Logger; + +public class BmlForm { + private static Logger logger = Logger.getLogger(BmlForm.class.getName()); + private final StringBuffer buf = new StringBuffer(); + private int openBorders = 0; + private int openCenters = 0; + private int openVarrays = 0; + private int openScrolls = 0; + private int openHarrays = 0; + private int openTrees = 0; + private int openRows = 0; + private int openColumns = 0; + private int openTables = 0; + private int indentNum = 0; + private boolean beautify = false; + private boolean closeDefault = false; + + public BmlForm() { + } + + public BmlForm(String formTitle) { + this.addDefaultHeader(formTitle); + } + + public void addDefaultHeader(String formTitle) { + if (this.closeDefault) { + return; + } + this.beginBorder(); + this.beginCenter(); + this.addBoldText(formTitle, new String[0]); + this.endCenter(); + this.beginScroll(); + this.beginVerticalFlow(); + this.closeDefault = true; + } + + public void beginBorder() { + this.buf.append(this.indent("border{")); + ++this.indentNum; + ++this.openBorders; + } + + public void endBorder() { + --this.indentNum; + this.buf.append(this.indent("}")); + --this.openBorders; + } + + public void beginCenter() { + this.buf.append(this.indent("center{")); + ++this.indentNum; + ++this.openCenters; + } + + public void endCenter() { + --this.indentNum; + this.buf.append(this.indent("};null;")); + --this.openCenters; + } + + public void beginVerticalFlow() { + this.buf.append(this.indent("varray{rescale=\"true\";")); + ++this.indentNum; + ++this.openVarrays; + } + + public void endVerticalFlow() { + --this.indentNum; + this.buf.append(this.indent("}")); + --this.openVarrays; + } + + public void beginScroll() { + this.buf.append(this.indent("scroll{vertical=\"true\";horizontal=\"false\";")); + ++this.indentNum; + ++this.openScrolls; + } + + public void endScroll() { + --this.indentNum; + this.buf.append(this.indent("};null;null;")); + --this.openScrolls; + } + + public void beginHorizontalFlow() { + this.buf.append(this.indent("harray {")); + ++this.indentNum; + ++this.openHarrays; + } + + public void endHorizontalFlow() { + --this.indentNum; + this.buf.append(this.indent("}")); + --this.openHarrays; + } + + public void beginTable(int rowCount, String[] columns) { + this.buf.append(this.indent("table {rows=\"" + rowCount + "\"; cols=\"" + columns.length + "\";")); + ++this.indentNum; + String[] arrstring = columns; + int n = arrstring.length; + int n2 = 0; + while (n2 < n) { + String c = arrstring[n2]; + this.addLabel(c); + ++n2; + } + --this.indentNum; + ++this.indentNum; + ++this.openTables; + } + + public void endTable() { + --this.indentNum; + this.buf.append(this.indent("}")); + --this.openTables; + } + + public void addHidden(String name, String val) { + this.buf.append(this.indent("passthrough{id=\"" + name + "\";text=\"" + val + "\"}")); + } + + private String indent(String s) { + return this.beautify ? String.valueOf(this.getIndentation()) + s + "\r\n" : s; + } + + private String getIndentation() { + if (this.indentNum > 0) { + return "\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t".substring(0, this.indentNum); + } + return ""; + } + + public void addRaw(String s) { + this.buf.append(s); + } + + public void addImage(String url, int height, int width) { + this.addImage(url, height, width, ""); + } + + public void addImage(String url, int height, int width, String tooltip) { + this.buf.append("image{src=\""); + this.buf.append(url); + this.buf.append("\";size=\""); + this.buf.append(String.valueOf(height) + "," + width); + this.buf.append("\";text=\"" + tooltip + "\"}"); + } + + public void addLabel(String text) { + this.buf.append("label{text='" + text + "'};"); + } + + public void addInput(String id, int maxChars, String defaultText) { + this.buf.append("input{id='" + id + "';maxchars='" + maxChars + "';text=\"" + defaultText + "\"};"); + } + + public void addColoredText(String text, int r, int g, int b, String ... args){ + this.addText(text, "", r, g, b, args); + } + + public void addBoldText(String text, String ... args) { + this.addText(text, "bold", args); + } + + public void addBoldColoredText(String text, int r, int g, int b, String ... args){ + this.addText(text, "bold", r, g, b, args); + } + + public void addText(String text, String ... args) { + this.addText(text, "", args); + } + + private void addText(String text, String type, String ... args){ + this.addText(text, type, -10, -10, -10, args); + } + private void addText(String text, String type, int r, int g, int b, String ... args) { + String[] lines; + String[] arrstring = lines = text.split("\n"); + int n = arrstring.length; + int n2 = 0; + while (n2 < n) { + String l = arrstring[n2]; + if (this.beautify) { + this.buf.append(this.getIndentation()); + } + this.buf.append("text{"); + if (!type.equals("")) { + this.buf.append("type='").append(type).append("';"); + } + if(r >= 0 && g >= 0 && b >= 0 && r <= 255 && g <= 255 && b <= 255){ + this.buf.append("color='").append(r).append(",").append(g).append(",").append(b).append("';"); + } + this.buf.append("text=\""); + this.buf.append(String.format(l, (Object[]) args)); + this.buf.append("\"}"); + if (this.beautify) { + this.buf.append("\r\n"); + } + ++n2; + } + } + + public void addButton(String name, String id) { + this.buf.append(this.indent("button{text=' " + name + " ';id='" + id + "'}")); + } + + public String toString() { + if (this.closeDefault) { + this.endVerticalFlow(); + this.endScroll(); + this.endBorder(); + this.closeDefault = false; + } + if (this.openCenters != 0 || this.openVarrays != 0 || this.openScrolls != 0 || this.openHarrays != 0 || this.openBorders != 0 || this.openTrees != 0 || this.openRows != 0 || this.openColumns != 0 || this.openTables != 0) { + logger.log(Level.SEVERE, "While finalizing BML unclosed (or too many closed) blocks were found (this will likely mean the BML will not work!): center: " + this.openCenters + " vert-flows: " + this.openVarrays + " scroll: " + this.openScrolls + " horiz-flows: " + this.openHarrays + " border: " + this.openBorders + " trees: " + this.openTrees + " rows: " + this.openRows + " columns: " + this.openColumns + " tables: " + this.openTables); + } + return this.buf.toString(); + } +} +