Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,171 @@ void register() {
return resultWithState(error[0] == null, error[0]);
}
}));
registerEditingTools();
}

/**
* The editing tools. Each is one undo step in the builder, validated exactly as the inspector
* validates the same change, and answers with the operation's details and fresh state.
*/
private void registerEditingTools() {
editTool("guibuilder_component_types", "component_types", false,
"Describes what the GUI Builder can build: every component type with the properties it accepts"
+ " (kind, default, range, choices), the layouts and their constraints, guided-layout size"
+ " policies and align actions, toolbar command placements and binding strategies. Read it"
+ " before adding components or setting properties.",
"{\"type\":\"object\",\"properties\":{}}");
editTool("guibuilder_get_form_xml", "get_form_xml", false,
"Returns the active form's .gui XML as it is in the editor, unsaved changes included, or one"
+ " component's subtree when component is given. Attribute names are lower case.",
"{\"type\":\"object\",\"properties\":{\"component\":{\"type\":\"string\"}}}");
editTool("guibuilder_add_component", "add_component", true,
"Adds a component, as the palette does, as one undo step. parent defaults to the form; index is"
+ " the position among the parent's children (not for TableLayout). Optionally name it and"
+ " set layout (for a Container: {layout, axis, rows, columns}), properties (see"
+ " guibuilder_component_types) and constraint (as guibuilder_set_constraint takes) in the"
+ " same step. A new Container defaults to LayeredLayout. Returns the component's name.",
"{\"type\":\"object\",\"properties\":{"
+ "\"type\":{\"type\":\"string\",\"enum\":" + json(PALETTE) + "},"
+ "\"parent\":{\"type\":\"string\"},\"index\":{\"type\":\"integer\",\"minimum\":0},"
+ "\"name\":{\"type\":\"string\"},\"layout\":{\"type\":\"object\"},"
+ "\"properties\":{\"type\":\"object\"},\"constraint\":{\"type\":\"object\"}},"
+ "\"required\":[\"type\"]}");
editTool("guibuilder_insert_xml", "insert_xml", true,
"Inserts a .gui fragment -- one <component> element with any nested components -- into parent"
+ " (default: the form) as one undo step. Builds a whole section in one call. Names that"
+ " are taken are made unique and references inside the fragment follow; the result lists"
+ " the names used. Types must be palette types; a table child gets a free cell.",
"{\"type\":\"object\",\"properties\":{\"xml\":{\"type\":\"string\"},"
+ "\"parent\":{\"type\":\"string\"},\"index\":{\"type\":\"integer\",\"minimum\":0}},"
+ "\"required\":[\"xml\"]}");
editTool("guibuilder_set_properties", "set_properties", true,
"Sets inspector properties of one component as one undo step, e.g. {\"text\":\"Sign in\","
+ "\"uiid\":\"PrimaryButton\",\"enabled\":false}. null resets a property to its default."
+ " Ranges and choices are checked; names, layouts, constraints, handlers and binding have"
+ " their own tools.",
"{\"type\":\"object\",\"properties\":{\"component\":{\"type\":\"string\"},"
+ "\"properties\":{\"type\":\"object\"}},\"required\":[\"component\",\"properties\"]}");
editTool("guibuilder_rename_component", "rename_component", true,
"Renames a component and repoints every layout relationship that referred to it. The name"
+ " becomes the generated Java field. Returns the name applied, which differs when the"
+ " requested one is taken.",
"{\"type\":\"object\",\"properties\":{\"component\":{\"type\":\"string\"},"
+ "\"name\":{\"type\":\"string\"}},\"required\":[\"component\",\"name\"]}");
editTool("guibuilder_delete_components", "delete_components", true,
"Deletes components (and everything inside them) as one undo step. The form itself cannot be"
+ " deleted.",
"{\"type\":\"object\",\"properties\":{\"components\":{\"type\":\"array\",\"items\":{\"type\":\"string\"},"
+ "\"minItems\":1}},\"required\":[\"components\"]}");
editTool("guibuilder_set_layout", "set_layout", true,
"Sets a container's layout: BoxLayout (axis X or Y), BorderLayout (at most five children),"
+ " FlowLayout, GridLayout (rows, columns 1-100), TableLayout (rows, columns; never below the"
+ " cells already used) or LayeredLayout (guided). Omit layout to change only axis, rows or"
+ " columns.",
"{\"type\":\"object\",\"properties\":{\"container\":{\"type\":\"string\"},"
+ "\"layout\":{\"type\":\"string\",\"enum\":" + json(LAYOUTS) + "},"
+ "\"axis\":{\"type\":\"string\",\"enum\":[\"X\",\"Y\"]},"
+ "\"rows\":{\"type\":\"integer\"},\"columns\":{\"type\":\"integer\"}},"
+ "\"required\":[\"container\"]}");
editTool("guibuilder_set_constraint", "set_constraint", true,
"Places a component within its parent, by the parent's layout. Any layout except TableLayout:"
+ " index (order among siblings). BorderLayout: region (North, South, East, West, Center;"
+ " an occupant swaps places). TableLayout: row, column, rowSpan, columnSpan, columnWidth and"
+ " rowHeight (percent, -1 automatic); the cells must be free. LayeredLayout (guided):"
+ " reference (sibling name, null for nearest), insets (\"top right bottom left\", each auto,"
+ " baseline or a number with px, mm or %), horizontalSize and verticalSize (preferred,"
+ " fixed, fill, match) and align (alignLeft, alignHCenter, alignRight, alignTop,"
+ " alignBaseline, alignBottom, matchWidth, matchHeight, fillWidth, fillHeight).",
"{\"type\":\"object\",\"properties\":{\"component\":{\"type\":\"string\"},"
+ "\"index\":{\"type\":\"integer\",\"minimum\":0},"
+ "\"region\":{\"type\":\"string\",\"enum\":[\"North\",\"South\",\"East\",\"West\",\"Center\"]},"
+ "\"row\":{\"type\":\"integer\"},\"column\":{\"type\":\"integer\"},"
+ "\"rowSpan\":{\"type\":\"integer\"},\"columnSpan\":{\"type\":\"integer\"},"
+ "\"columnWidth\":{\"type\":\"integer\"},\"rowHeight\":{\"type\":\"integer\"},"
+ "\"reference\":{\"type\":[\"string\",\"null\"]},\"insets\":{\"type\":\"string\"},"
+ "\"horizontalSize\":{\"type\":\"string\",\"enum\":[\"preferred\",\"fixed\",\"fill\",\"match\"]},"
+ "\"verticalSize\":{\"type\":\"string\",\"enum\":[\"preferred\",\"fixed\",\"fill\",\"match\"]},"
+ "\"align\":{\"type\":[\"string\",\"array\"],\"items\":{\"type\":\"string\"}}},"
+ "\"required\":[\"component\"]}");
editTool("guibuilder_set_event_handler", "set_event_handler", true,
"Attaches an action handler method to a Button, CheckBox, RadioButton, TextField, TextArea or"
+ " Slider (handler null removes it). Saving the form adds an empty"
+ " protected void <handler>(ActionEvent event) to the companion source's user code region;"
+ " the result names that file. Write the method body there.",
"{\"type\":\"object\",\"properties\":{\"component\":{\"type\":\"string\"},"
+ "\"handler\":{\"type\":[\"string\",\"null\"]}},\"required\":[\"component\",\"handler\"]}");
editTool("guibuilder_toolbar_commands", "toolbar_commands", true,
"Lists, adds, updates or removes the form's toolbar commands. A command has a label, a placement"
+ " (left, right, overflow, side) and a handler method. update and remove take the index"
+ " from list. Returns the commands.",
"{\"type\":\"object\",\"properties\":{"
+ "\"action\":{\"type\":\"string\",\"enum\":[\"list\",\"add\",\"update\",\"remove\"]},"
+ "\"index\":{\"type\":\"integer\",\"minimum\":0},\"label\":{\"type\":\"string\"},"
+ "\"placement\":{\"type\":\"string\",\"enum\":[\"left\",\"right\",\"overflow\",\"side\"]},"
+ "\"handler\":{\"type\":\"string\"}}}");
editTool("guibuilder_set_binding", "set_binding", true,
"Sets the form's data binding strategy: none, properties (a PropertyBusinessObject model) or"
+ " bindable (a @Bindable POJO). The <Form>Model class is created on save when missing; an"
+ " existing model is kept, never overwritten.",
"{\"type\":\"object\",\"properties\":{\"strategy\":{\"type\":\"string\","
+ "\"enum\":[\"none\",\"properties\",\"bindable\"]}},\"required\":[\"strategy\"]}");
editTool("guibuilder_create_form", "create_form", true,
"Creates a new form (.gui plus its companion Java class), opens it and saves it so it compiles."
+ " className is fully qualified, e.g. com.example.myapp.LoginForm. type: Form (default),"
+ " Dialog or Container. layout defaults to BoxLayout (vertical). Refuses while the active"
+ " form has unsaved changes and never overwrites an existing file.",
"{\"type\":\"object\",\"properties\":{\"className\":{\"type\":\"string\"},"
+ "\"type\":{\"type\":\"string\",\"enum\":[\"Form\",\"Dialog\",\"Container\"]},"
+ "\"layout\":{\"type\":\"string\",\"enum\":" + json(LAYOUTS) + "}},"
+ "\"required\":[\"className\"]}");
}

private static final String[] PALETTE = com.codename1.guibuilder.model.ComponentCatalog.PALETTE_TYPES;
private static final String[] LAYOUTS = com.codename1.guibuilder.model.ComponentCatalog.LAYOUTS;

private static String json(String[] values) {
StringBuilder out = new StringBuilder("[");
for (int i = 0; i < values.length; i++) {
if (i > 0) out.append(',');
out.append('"').append(values[i]).append('"');
}
return out.append(']').toString();
}

private void editTool(String name, final String operation, final boolean includeState, String description,
String schema) {
MCP.addTool(new Tool(name, description, schema, new ToolHandler() {
@Override public String invoke(String argumentsJson) throws Exception {
return JSONParser.toJson(edit(operation, parseKeepingNulls(argumentsJson), includeState));
}
}));
}

/**
* Runs one editing operation on the EDT.
*
* @param operation the builder operation
* @param args the parsed arguments
* @param includeState whether to append fresh state, as every mutating tool does
* @return the result map: success, error, the operation's details and state
*/
Map<String, Object> edit(final String operation, final Map<String, Object> args, boolean includeState) {
final Map<String, Object>[] holder = new Map[1];
runOnEdt(new Runnable() {
@Override public void run() { holder[0] = builder.mcpEdit(operation, args); }
});
Map<String, Object> details = holder[0] == null ? new LinkedHashMap<String, Object>() : holder[0];
Map<String, Object> result = new LinkedHashMap<String, Object>();
Object error = details.remove("error");
result.put("success", Boolean.valueOf(error == null));
if (error != null) result.put("error", error);
result.putAll(details);
if (includeState) {
// The rebuild queues the overlay refresh; cross one EDT turn so state is settled.
runOnEdt(new Runnable() { @Override public void run() { } });
result.put("state", onEdtState());
}
return result;
}

void record(String kind, Map<String, Object> details) {
Expand Down Expand Up @@ -223,6 +388,20 @@ private static Map<String, Object> parse(String json) throws Exception {
return parsed == null ? new LinkedHashMap<String, Object>() : parsed;
}

/**
* Parses arguments with explicit nulls kept. The editing tools give null a meaning -- reset a
* property, remove a handler, clear a reference -- and the default parser drops null members,
* which turned "reset text" into an empty request.
*/
private static Map<String, Object> parseKeepingNulls(String json) throws Exception {
if (json == null || json.length() == 0) return new LinkedHashMap<String, Object>();
JSONParser parser = new JSONParser();
parser.setIncludeNullsInstance(true);
Map<String, Object> parsed = parser.parseJSON(new java.io.InputStreamReader(
new java.io.ByteArrayInputStream(json.getBytes("UTF-8")), "UTF-8"));
return parsed == null ? new LinkedHashMap<String, Object>() : parsed;
}

private static long longValue(Object value, long fallback) {
return value instanceof Number ? ((Number) value).longValue() : fallback;
}
Expand Down
Loading
Loading