/*
* Beryl - A web platform based on XML, XSLT and Java
* This file is part of the Beryl XML GUI
*
* Copyright (C) 2004 Wenzel Jakob <wazlaf@tigris.org>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-3107 USA
*/
package org.beryl.gui.builder;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import javax.swing.JComponent;
import org.beryl.gui.AnchorFactory;
import org.beryl.gui.Controller;
import org.beryl.gui.GUIEvent;
import org.beryl.gui.GUIEventListener;
import org.beryl.gui.GUIException;
import org.beryl.gui.InternationalizationManager;
import org.beryl.gui.LayoutFactory;
import org.beryl.gui.MessageDialog;
import org.beryl.gui.PropertyFactory;
import org.beryl.gui.Widget;
import org.beryl.gui.WidgetFactory;
import org.beryl.gui.WidgetInfo;
import org.beryl.gui.XMLUtils;
import org.beryl.gui.model.MapChangeEvent;
import org.beryl.gui.model.MapDataModel;
import org.beryl.gui.model.ModelChangeEvent;
import org.beryl.gui.model.ModelChangeListener;
import org.beryl.gui.model.TableDataModel;
import org.beryl.gui.model.TableRow;
import org.beryl.gui.widgets.Dialog;
import org.beryl.gui.widgets.Frame;
import org.beryl.gui.widgets.Panel;
import org.beryl.gui.widgets.PopupMenu;
import org.beryl.gui.widgets.Table;
import org.beryl.gui.widgets.Tree;
import org.beryl.gui.widgets.TreeItem;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
public class WidgetTree extends Controller implements ModelChangeListener {
private static PropertyFactory pf = PropertyFactory.getInstance();
private static LayoutFactory lf = LayoutFactory.getInstance();
private static AnchorFactory af = AnchorFactory.getInstance();
/**
* The widget tree frame
*/
private Frame frame = null;
/**
* The data model
*/
private MapDataModel dataModel = null;
/**
* The tree component
*/
private Tree widgetTree = null;
/**
* Root node
*/
private TreeItem rootNode = null;
/**
* Tree popup menu
*/
private PopupMenu treePopupMenu = null;
/**
* Property popup menu
*/
private PopupMenu propertyPopupMenu = null;
/**
* Property table
*/
private Table table = null;
/**
* Empty table data model
*/
private TableDataModel emptyModel = null;
/* DEBUGGING */
private GUIEventListener debuggingGUIEventListener = null;
private MapDataModel debuggingMapDataModel = null;
private class DebuggingModelChangeListener implements ModelChangeListener {
public void modelChanged(ModelChangeEvent e) {
if (e instanceof MapChangeEvent) {
MapChangeEvent event = (MapChangeEvent) e;
log.debug("Data model change: '" + event.getKey() + "' => '" + event.getNewValue() + "'");
} else {
log.debug("Data model change: " + e.toString());
}
}
};
public WidgetTree(Builder builder) throws GUIException {
dataModel = new MapDataModel();
frame = constructFrame("WidgetTree", dataModel);
treePopupMenu = (PopupMenu) constructWidget("WidgetPopup");
propertyPopupMenu = (PopupMenu) constructWidget("PropertyPopup");
emptyModel = new TableDataModel();
widgetTree = (Tree) frame.getWidget("Tree");
table = (Table) frame.getWidget("Table");
rootNode = (TreeItem) frame.getWidget("RootNode");
table.setTableDataModel(emptyModel);
debuggingMapDataModel = new MapDataModel();
debuggingMapDataModel.addModelChangeListener(new DebuggingModelChangeListener());
dataModel.addModelChangeListener(this);
}
public void refresh(Document document) throws GUIException {
for (int i = 0; i < rootNode.getChildCount(); i++) {
TreeItem item = (TreeItem) rootNode.getChild(i);
WidgetUserObject object = (WidgetUserObject) item.getUserObject();
if (object.widget instanceof Frame) {
((Frame) object.widget).dispose();
} else if (object.widget instanceof Dialog) {
((Dialog) object.widget).dispose();
}
}
rootNode.removeAllChildren();
NodeList children = document.getDocumentElement().getChildNodes();
for (int i = 0; i < children.getLength(); i++) {
Node node = (Node) children.item(i);
if (node.getNodeType() == Node.ELEMENT_NODE && node.getNodeName().equals("widget")) {
traverseDocument((Element) node, rootNode);
}
}
rootNode.setUserObject(new WidgetUserObject(document.getDocumentElement(), null, rootNode));
widgetTree.structureChanged(rootNode);
System.out.println(frame.dumpStructure());
}
/**
* Traverses the XML tree, creates the preview
* and builds the widget tree
*/
private Widget traverseDocument(Element xmlNode, TreeItem parentNode) throws GUIException {
TreeItem newNode = new TreeItem(parentNode, null);
WidgetUserObject parent = (WidgetUserObject) parentNode.getUserObject();
String classAttribute = xmlNode.getAttribute("class");
String nameAttribute = xmlNode.getAttribute("name");
String presetAttribute = xmlNode.getAttribute("preset");
if (presetAttribute.equals(""))
presetAttribute = null;
if (nameAttribute.equals(""))
nameAttribute = null;
Widget widget = constructWidget(parent.widget, classAttribute, nameAttribute, presetAttribute);
WidgetUserObject object = new WidgetUserObject(xmlNode, widget, newNode);
newNode.setUserObject(object);
newNode.setText(describeWidget(xmlNode));
NodeList childNodes = xmlNode.getChildNodes();
object.tableModel.addRow(new NameTableRow(object));
Element anchorNode = XMLUtils.getChild(xmlNode, "anchor");
if (anchorNode != null) {
object.tableModel.addRow(new PropertyTableRow(object, null, anchorNode));
}
for (int o = 0; o < childNodes.getLength(); o++) {
if (childNodes.item(o).getNodeName().equals("property")) {
Element propertyNode = (Element) childNodes.item(o);
String propertyName = propertyNode.getAttribute("name");
Object propertyValue = pf.constructProperty(propertyNode);
object.tableModel.addRow(new PropertyTableRow(object, propertyValue, propertyNode));
widget.setProperty(propertyName, propertyValue);
} else if (childNodes.item(o).getNodeName().equals("layout")) {
Element layoutNode = (Element) childNodes.item(o);
Object propertyValue = lf.constructLayout(widget, layoutNode);
object.tableModel.addRow(new PropertyTableRow(object, propertyValue, layoutNode));
widget.setProperty("layout", propertyValue);
} else if (childNodes.item(o).getNodeName().equals("emit")) {
Element emitNode = (Element) childNodes.item(o);
String name = emitNode.getAttribute("name");
TableRow row = new TableRow();
row.setValue("event", emitNode.getAttribute("event"));
row.setValue("name", name);
row.setValue(
"description",
widget.getWidgetInfo().getEventEntry(emitNode.getAttribute("event")).getDescription());
row.setValue("node", emitNode);
object.eventModel.addRow(row);
}
}
for (int i = 0; i < childNodes.getLength(); i++) {
if (childNodes.item(i).getNodeName().equals("widget")) {
Element childNode = (Element) childNodes.item(i);
/* Get the child's anchor subnode */
anchorNode = XMLUtils.getChild(childNode, "anchor");
Widget childWidget = traverseDocument(childNode, newNode);
widget.addChild(childWidget, createAnchor(childWidget, anchorNode));
}
}
parentNode.addChild(newNode, null);
widget.finalizeConstruction();
if (widget instanceof Dialog) {
/* Dialogs get treated a little bit differently */
Dialog dialog = (Dialog) widget;
dialog.initDialog(null);
dialog.show();
}
return widget;
}
public static Object createAnchor(Widget widget, Element anchorNode) throws GUIException {
Object anchor = null;
if (anchorNode != null) {
anchor = af.constructAnchor(anchorNode);
if (anchor instanceof AnchorFactory.BoxAnchor) {
JComponent component = (JComponent) widget.getWidget();
AnchorFactory.BoxAnchor ba = (AnchorFactory.BoxAnchor) anchor;
component.setAlignmentX(ba.getAlignmentX());
component.setAlignmentY(ba.getAlignmentY());
anchor = null;
}
} else {
Object comp = widget.getWidget();
if (comp != null && comp instanceof JComponent) {
JComponent component = (JComponent) comp;
component.setAlignmentX(0.0f);
component.setAlignmentY(0.0f);
}
}
return anchor;
}
/**
* Return a string description of a widget element
*/
public static String describeWidget(Element element) {
StringBuffer buf = new StringBuffer();
String classAttribute = element.getAttribute("class");
String nameAttribute = element.getAttribute("name");
String presetAttribute = element.getAttribute("preset");
buf.append(classAttribute);
if (!nameAttribute.equals("")) {
buf.append(" (").append(nameAttribute).append(")");
}
if (!presetAttribute.equals("")) {
buf.append(" [").append(presetAttribute).append("]");
}
return buf.toString();
}
public void doInsert(String className) throws GUIException {
TreeItem items[] = (TreeItem[]) dataModel.getValue("tree.selected");
if (items.length == 1) {
WidgetInfo info = constructWidget(null, className, null, null).getWidgetInfo();
Element anchorElement =
((WidgetUserObject) rootNode.getUserObject()).element.getOwnerDocument().createElement("anchor");
new InsertDialog(this, className, info, anchorElement);
}
}
public void doInsert(String className, String preset, Element anchorNode) throws GUIException {
TreeItem items[] = (TreeItem[]) dataModel.getValue("tree.selected");
TreeItem item = items[0];
WidgetUserObject parentObject = (WidgetUserObject) item.getUserObject();
Document document = parentObject.element.getOwnerDocument();
Element childWidget = document.createElement("widget");
childWidget.setAttribute("class", className);
if (preset != null)
childWidget.setAttribute("preset", preset);
TreeItem newNode = new TreeItem(item, null);
Widget parentWidget = ((WidgetUserObject) item.getUserObject()).widget;
Widget widget = constructWidget(parentWidget, className, null, preset);
Object anchor = null;
if (parentWidget != null) {
anchor = createAnchor(widget, anchorNode);
parentWidget.addChild(widget, anchor);
}
widget.finalizeConstruction();
parentObject.element.appendChild(childWidget);
if (widget instanceof Dialog) {
Dialog dialog = (Dialog) widget;
dialog.initDialog(null);
dialog.show();
}
if (parentWidget != null)
revalidate(parentWidget);
WidgetUserObject userObject = new WidgetUserObject(childWidget, widget, newNode);
userObject.tableModel.addRow(new NameTableRow(userObject));
if (anchorNode != null) {
childWidget.appendChild(anchorNode);
PropertyTableRow row = new PropertyTableRow(userObject, anchor, anchorNode);
userObject.tableModel.addRow(row);
}
newNode.setUserObject(userObject);
newNode.setText(className);
item.addChild(newNode, null);
widgetTree.structureChanged(item);
dataModel.setValue("tree.selected", new TreeItem[] { newNode });
Builder.markModified();
}
private Widget constructWidget(Widget parentWidget, String className, String widgetName, String preset)
throws GUIException {
try {
if (className.indexOf('.') == -1)
className = WidgetFactory.DEFAULT_PREFIX + className;
Class wClass = Class.forName(className);
Constructor widgetConstructor = null;
Widget widget = null;
if (preset != null)
widgetConstructor = wClass.getConstructor(new Class[] { Widget.class, String.class, String.class });
else
widgetConstructor = wClass.getConstructor(new Class[] { Widget.class, String.class });
if (preset != null)
widget = (Widget) widgetConstructor.newInstance(new Object[] { parentWidget, widgetName, preset });
else
widget = (Widget) widgetConstructor.newInstance(new Object[] { parentWidget, widgetName });
widget.setDataModel(debuggingMapDataModel);
return widget;
} catch (ClassNotFoundException e) {
throw new GUIException("Unknown widget class [" + className + "]");
} catch (NoSuchMethodException e) {
throw new GUIException("Widget constructor not found", e);
} catch (IllegalAccessException e) {
throw new GUIException("Widget constructor could not be called", e);
} catch (InstantiationException e) {
throw new GUIException("Widget is abstract", e);
} catch (InvocationTargetException e) {
throw new GUIException("Widget constructor threw an exception", e);
}
}
private void doDelete(TreeItem item) throws GUIException {
TreeItem parent = (TreeItem) item.getParent();
WidgetUserObject object = (WidgetUserObject) item.getUserObject();
object.element.getParentNode().removeChild(object.element);
Widget parentWidget = object.widget.getParentWidget();
if (object.widget.getParentWidget() != null) {
object.widget.getParentWidget().removeChildWidget(object.widget);
revalidate(parentWidget);
} else {
if (object.widget instanceof Frame) {
((Frame) object.widget).dispose();
} else if (object.widget instanceof Dialog) {
((Dialog) object.widget).dispose();
}
}
parent.remove(item);
widgetTree.structureChanged(parent);
Builder.markModified();
}
public static void revalidate(Widget widget) throws GUIException {
if (widget instanceof Panel)
((Panel) widget).recreateLayout();
else
widget.revalidate();
}
/**
* Removes and then re-inserts all children of the given widget's parent
*/
public static void doReInsert(WidgetUserObject object) throws GUIException {
ArrayList temp = new ArrayList(), tempAnchors = new ArrayList();
Widget parentWidget = object.widget.getParentWidget();
TreeItem parentNode = (TreeItem) object.treeNode.getParentWidget();
for (int i=0, count=parentNode.getChildCount(); i<count; i++) {
TreeItem node = (TreeItem) parentNode.getChild(i);
WidgetUserObject nodeObj = (WidgetUserObject) node.getUserObject();
temp.add(nodeObj.widget);
tempAnchors.add(XMLUtils.getChild(nodeObj.element, "anchor"));
parentWidget.removeChildWidget(nodeObj.widget);
}
for (int i=0; i<temp.size(); i++) {
Object anchor = createAnchor((Widget) temp.get(i), (Element) tempAnchors.get(i));
parentWidget.addChild((Widget) temp.get(i), anchor);
}
revalidate(parentWidget);
}
public static void doDeleteProperty(PropertyTableRow row) throws GUIException {
WidgetUserObject object = row.getUserObject();
String name = (String) row.getValue("name");
if (name.equals("anchor")) {
row.getPropertyNode().getParentNode().removeChild(row.getPropertyNode());
doReInsert(object);
} else {
WidgetInfo.PropertyEntry entry = object.widget.getWidgetInfo().getPropertyEntry(name);
MapChangeEvent evt = new MapChangeEvent(null, null, "value", null, entry.defaultValue);
row.modelChanged(evt);
row.getPropertyNode().getParentNode().removeChild(row.getPropertyNode());
}
object.tableModel.removeRow(row);
}
/**
* For simplicity's sake, the XML GUI does not have insertAt style methods
* Therefore we now must remove everything and then add it in a different order
*/
private void doMove(TreeItem moveItem, boolean up) throws GUIException {
WidgetUserObject object = (WidgetUserObject) moveItem.getUserObject();
ArrayList temp = new ArrayList();
/* Update tree */
TreeItem parentTreeItem = (TreeItem) moveItem.getParentWidget();
int treeIndex = parentTreeItem.getChildIndex(moveItem);
if (treeIndex == -1)
throw new GUIException("Tree node not found");
for (int i=0, count=parentTreeItem.getChildCount(); i<count; i++) {
TreeItem item = (TreeItem) parentTreeItem.getChild(0);
if (item != moveItem)
temp.add(item);
parentTreeItem.removeChildWidget(item);
}
temp.add(treeIndex + (up ? -1 : 1), moveItem);
for (int i=0; i<temp.size(); i++)
parentTreeItem.addChild((Widget) temp.get(i), null);
temp.clear();
/* Update widget */
Widget parentWidget = object.widget.getParentWidget();
int widgetIndex = parentWidget.getChildIndex(object.widget);
if (widgetIndex == -1)
throw new GUIException("Widget not found");
for (int i=0, count=parentWidget.getChildCount(); i<count; i++) {
Widget widget = (Widget) parentWidget.getChild(0);
if (widget != object.widget)
temp.add(widget);
parentWidget.removeChildWidget(widget);
}
temp.add(widgetIndex + (up ? -1 : 1), object.widget);
for (int i=0; i<temp.size(); i++) {
Element anchorNode = XMLUtils.getChild(object.element, "anchor");
Object anchor = null;
if (anchorNode != null)
anchor = AnchorFactory.getInstance().constructAnchor(anchorNode);
parentWidget.addChild((Widget) temp.get(i), anchor);
}
/* Update XML - This is horrible */
Element parentElement = (Element) object.element.getParentNode();
NodeList nodes = parentElement.getChildNodes();
Element lastWidget = null, beforeWidget = null;
int distance = -1;
for (int i=0; i<nodes.getLength(); i++) {
Node node = nodes.item(i);
if (node == object.element) {
if (up) {
beforeWidget = lastWidget;
break;
} else {
distance = 1;
}
} else if (node instanceof Element) {
Element element = (Element) node;
if (element.getNodeName().equals("widget")) {
lastWidget = element;
if (distance == 0) {
beforeWidget = lastWidget;
break;
}
}
if (distance != -1)
distance--;
}
}
parentElement.removeChild(object.element);
parentElement.insertBefore(object.element, beforeWidget);
revalidate(parentWidget);
widgetTree.structureChanged(parentTreeItem);
Builder.markModified();
}
public Frame getFrame() {
return frame;
}
public void eventOccured(GUIEvent event) {
String command = event.getName();
try {
if (command.equals("tree.popup")) {
TreeItem items[] = (TreeItem[]) dataModel.getValue("tree.selected");
if (items.length > 0) {
boolean notRoot = items.length > 1 || (items.length == 1 && items[0] != rootNode);
boolean moveUp =
items.length == 1
&& items[0] != rootNode
&& (items[0].getParentWidget().getChildIndex(items[0]) > 0);
boolean moveDown =
items.length == 1
&& items[0] != rootNode
&& (items[0].getParentWidget().getChildIndex(items[0])
< items[0].getParentWidget().getChildCount() - 1);
treePopupMenu.getWidget("Delete").setEnabled(notRoot);
treePopupMenu.getWidget("MoveUp").setEnabled(moveUp);
treePopupMenu.getWidget("MoveDown").setEnabled(moveDown);
treePopupMenu.popup(event);
}
} else if (command.equals("property.popup")) {
propertyPopupMenu.popup(event);
} else if (command.equals("delete")) {
TreeItem items[] = (TreeItem[]) dataModel.getValue("tree.selected");
for (int i = 0; i < items.length; i++) {
if (items[i] != rootNode)
doDelete(items[i]);
}
} else if (command.equals("add_property")) {
TreeItem items[] = (TreeItem[]) dataModel.getValue("tree.selected");
if (items.length == 1 && items[0] != rootNode) {
new AddPropertyDialog(frame, (WidgetUserObject) items[0].getUserObject());
}
} else if (command.equals("delete_property")) {
TableRow rows[] = (TableRow[]) dataModel.getValue("property.value");
for (int i = 0; i < rows.length; i++) {
if (rows[i] instanceof PropertyTableRow) {
doDeleteProperty((PropertyTableRow) rows[i]);
Builder.markModified();
}
}
} else if (command.equals("events")) {
TreeItem items[] = (TreeItem[]) dataModel.getValue("tree.selected");
if (items.length == 1 && items[0] != rootNode) {
new EventDialog(this, frame, (WidgetUserObject) items[0].getUserObject());
}
} else if (command.equals("move_up")) {
TreeItem items[] = (TreeItem[]) dataModel.getValue("tree.selected");
if (items.length == 1 && items[0] != rootNode) {
doMove(items[0], true);
}
} else if (command.equals("move_down")) {
TreeItem items[] = (TreeItem[]) dataModel.getValue("tree.selected");
if (items.length == 1 && items[0] != rootNode) {
doMove(items[0], false);
}
}
} catch (Exception e) {
new MessageDialog(e);
}
}
public void modelChanged(ModelChangeEvent e) throws GUIException {
if (e instanceof MapChangeEvent) {
MapChangeEvent event = (MapChangeEvent) e;
if (event.getKey().equals("tree.selected")) {
TreeItem items[] = (TreeItem[]) event.getNewValue();
dataModel.setValue("property.value", new TableRow[] {
});
if (items.length == 1 && items[0] != rootNode) {
WidgetUserObject object = (WidgetUserObject) items[0].getUserObject();
table.setTableDataModel(object.tableModel);
} else {
table.setTableDataModel(emptyModel);
}
}
}
}
public void refreshInternationalProperties() throws GUIException {
refreshInternationalProperties(rootNode);
}
private void refreshInternationalProperties(TreeItem treeItem) throws GUIException {
WidgetUserObject object = (WidgetUserObject) treeItem.getUserObject();
if (object.element != null) {
NodeList nodes = object.element.getChildNodes();
for (int i = 0; i < nodes.getLength(); i++) {
Node node = nodes.item(i);
if (node instanceof Element && node.getNodeName().equals("property")) {
Element propertyNode = (Element) node;
String type = propertyNode.getAttribute("type");
String name = propertyNode.getAttribute("name");
if (type.equals("") || type.equals("istring")) {
String text = XMLUtils.extractTextChildren(propertyNode);
try {
object.widget.setProperty(name, InternationalizationManager.getString(text));
} catch (Exception e) {
log.warn(
"error while refreshing property ["
+ name
+ "] with internationalization ["
+ text
+ "]",
e);
}
}
}
}
}
for (int i = 0; i < treeItem.getChildCount(); i++)
refreshInternationalProperties((TreeItem) treeItem.getChild(i));
}
}