package com.github.czyzby.lml.parser.impl.tag.macro; import com.badlogic.gdx.scenes.scene2d.Actor; import com.badlogic.gdx.scenes.scene2d.ui.Cell; import com.badlogic.gdx.scenes.scene2d.ui.Table; import com.badlogic.gdx.utils.ObjectMap; import com.badlogic.gdx.utils.ObjectMap.Entry; import com.badlogic.gdx.utils.ObjectSet; import com.github.czyzby.kiwi.util.common.Strings; import com.github.czyzby.kiwi.util.gdx.collection.GdxMaps; import com.github.czyzby.kiwi.util.gdx.collection.GdxSets; import com.github.czyzby.lml.parser.LmlParser; import com.github.czyzby.lml.parser.LmlSyntax; import com.github.czyzby.lml.parser.impl.attribute.table.cell.AbstractCellLmlAttribute; import com.github.czyzby.lml.parser.impl.tag.AbstractMacroLmlTag; import com.github.czyzby.lml.parser.tag.LmlActorBuilder; import com.github.czyzby.lml.parser.tag.LmlAttribute; import com.github.czyzby.lml.parser.tag.LmlBuildingAttribute; import com.github.czyzby.lml.parser.tag.LmlTag; import com.github.czyzby.lml.util.LmlUserObject.StandardTableTarget; import com.github.czyzby.lml.util.LmlUserObject.TableTarget; /** Tables utility. Allows to append an empty cell to a table, without having to use mock-up actors to fill it. For * example: <blockquote> * * <pre> * <table> * First cell. * <:cell/> * Third cell. * </table> * </pre> * * </blockquote> Thanks to cell macro usage, such table would have 3 cells total. Cell macro tag can have any cell * attributes if you feel the need to customize it:<blockquote> * * <pre> * <table> * First row. * <:cell row="true" pad="8"/> * Second row. * </table> * </pre> * * </blockquote>However, you should generally use row macro to handle row defaults and append new rows. * * @author MJ * @see TableRowLmlMacroTag * @see TableColumnLmlMacroTag */ public class TableCellLmlMacroTag extends AbstractMacroLmlTag { public TableCellLmlMacroTag(final LmlParser parser, final LmlTag parentTag, final StringBuilder rawTagData) { super(parser, parentTag, rawTagData); } @Override protected boolean supportsNamedAttributes() { return true; } @Override public void handleDataBetweenTags(final CharSequence rawData) { if (Strings.isNotBlank(rawData)) { getParser().throwErrorIfStrict("Table cell macro cannot parse text between tags."); } } @Override public void closeTag() { processCell(); } /** This method is invoked when the tag is closed. Extracts a cell from the table. */ protected void processCell() { final ObjectMap<String, String> attributes = getNamedAttributes(); if (GdxMaps.isEmpty(attributes)) { processCellWithNoAttributes(getTable()); return; } final LmlActorBuilder builder = new LmlActorBuilder(); // Used to determine table. final ObjectSet<String> processedAttributes = GdxSets.newSet(); processBuildingAttributeToDetermineTable(attributes, processedAttributes, builder); final Table targetTable = getTarget(builder).extract(getTable()); final Cell<?> cell = extractCell(targetTable); processCellAttributes(attributes, processedAttributes, targetTable, cell); } /** @param table should have a cell extracted. * @return cell selected by the macro. By default, adds a new cell to the table. */ protected Cell<?> extractCell(final Table table) { return table.add(); } /** This method is invoked if the cell macro has no additional attributes. * * @param table is a parent of the macro tag. */ protected void processCellWithNoAttributes(final Table table) { // Adding empty cell: table.add(); } /** @return table extracted from parent tag. */ protected Table getTable() { final Actor table = getParent().getActor(); if (table instanceof Table) { return (Table) table; } getParser().throwError( getTagName() + " macro can be used only inside table tags (or one of its subclasses). Found \"" + getTagName() + "\" tag with no direct table parent."); return null; } /** @param builder may contain specific table target. * @return table target chosen by the builder. */ protected TableTarget getTarget(final LmlActorBuilder builder) { return builder.getTableTarget() == null ? StandardTableTarget.MAIN : builder.getTableTarget(); } /** This is meant to handle toButtonTable, toTitleTable, toDialogTable to choose which table should have a row * appended. * * @param attributes named attributes of the macro. * @param processedAttributes should contain processed building attributes after this method invocation. * @param builder used to process named attributes. */ protected void processBuildingAttributeToDetermineTable(final ObjectMap<String, String> attributes, final ObjectSet<String> processedAttributes, final LmlActorBuilder builder) { final LmlSyntax syntax = getParser().getSyntax(); for (final Entry<String, String> attribute : attributes) { final LmlBuildingAttribute<LmlActorBuilder> buildingAttribute = syntax .getBuildingAttributeProcessor(builder, attribute.key); if (buildingAttribute != null) { buildingAttribute.process(getParser(), getParent(), builder, attribute.value); processedAttributes.add(attribute.key); } } } /** This is meant to handle cell attributes that will modify the extracted cell. * * @param attributes named attributes of the macro. * @param processedAttributes already processed attributes. Should be ignored. * @param table owner of the cell. * @param cell cell of the row. Should have its defaults set. */ protected void processCellAttributes(final ObjectMap<String, String> attributes, final ObjectSet<String> processedAttributes, final Table table, final Cell<?> cell) { final LmlSyntax syntax = getParser().getSyntax(); for (final Entry<String, String> attribute : attributes) { if (processedAttributes.contains(attribute.key)) { continue; } final LmlAttribute<?> cellAttribute = syntax.getAttributeProcessor(table, attribute.key); if (cellAttribute instanceof AbstractCellLmlAttribute) { ((AbstractCellLmlAttribute) cellAttribute).process(getParser(), getParent(), table, cell, attribute.value); } else { if (!isInternalMacroAttribute(attribute.key)) { getParser().throwErrorIfStrict(getTagName() + " macro can process only cell attributes. Found unknown or invalid attribute: " + attribute.key); } } } } /** @param key lower-case attribute name present in the tag. * @return true if the attribute is used internally by the macro and should not be processed by the cell. */ protected boolean isInternalMacroAttribute(final String key) { // Basic cell macro has no internal attributes. return false; } }