import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.net.URLDecoder;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import org.w3c.dom.Document;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import com.sun.org.apache.xerces.internal.parsers.DOMParser;
import edu.cmu.cs.hcii.cogtool.CogToolPrefs;
import edu.cmu.cs.hcii.cogtool.model.AAction;
import edu.cmu.cs.hcii.cogtool.model.AShape;
import edu.cmu.cs.hcii.cogtool.model.CheckBox;
import edu.cmu.cs.hcii.cogtool.model.DesignUtil;
import edu.cmu.cs.hcii.cogtool.model.DeviceType;
import edu.cmu.cs.hcii.cogtool.model.DoubleRectangle;
import edu.cmu.cs.hcii.cogtool.model.GridButtonGroup;
import edu.cmu.cs.hcii.cogtool.model.IWidget;
import edu.cmu.cs.hcii.cogtool.model.ImportConverter;
import edu.cmu.cs.hcii.cogtool.model.ButtonAction;
import edu.cmu.cs.hcii.cogtool.model.Design;
import edu.cmu.cs.hcii.cogtool.model.Frame;
import edu.cmu.cs.hcii.cogtool.model.MouseButtonState;
import edu.cmu.cs.hcii.cogtool.model.MousePressType;
import edu.cmu.cs.hcii.cogtool.model.RadioButtonGroup;
import edu.cmu.cs.hcii.cogtool.model.ShapeRectangle;
import edu.cmu.cs.hcii.cogtool.model.SimpleWidgetGroup;
import edu.cmu.cs.hcii.cogtool.model.Transition;
import edu.cmu.cs.hcii.cogtool.model.Widget;
import edu.cmu.cs.hcii.cogtool.model.WidgetAttributes;
import edu.cmu.cs.hcii.cogtool.model.WidgetType;
import edu.cmu.cs.hcii.cogtool.util.ObjectLoader;
import edu.cmu.cs.hcii.cogtool.util.RcvrImportException;
public class BalsamiqButtonAPIConverter extends ImportConverter{
//TODO: Organize these into balsamiq and cogtool constants
public static final String DEVICE_ELT = "device";
public static final String FRAME_ELT = "frame";
public static final String NAME_ATTR = "name";
public static final String CONTROLS_ELT = "controls";
public static final String CONTROL_ELT = "control";
public static final String CONTROL_PROP_ELT = "controlproperties";
public static final String HREF_ELT = "href";
public static final String STATE_ELT = "state";
public static final String BALSAMIQ_TEXT_ELT = "text";
public static final String MOCKUP_ELT = "mockup";
public static final String DEMONSTRATION_ELT = "demonstration";
public static final String KEYBOARD_DEVICE = "keyboard";
public static final String MOUSE_DEVICE = "mouse";
public static final String TOUCHSCREEN_DEVICE = "touchscreen";
public static final String MICROPHONE_DEVICE = "microphone";
public static final String DISPLAY_DEVICE = "display";
public static final String SPEAKER_DEVICE = "speaker";
public static final String CONTROL_TYPE_ATTR = "controlTypeID";
public static final String CONTROL_ID_ATTR = "controlID";
public static final String BUTTON_WIDGETTYPE = "button";
public static final String LINK_WIDGETTYPE = "link";
public static final String CHECKBOX_WIDGETTYPE = "check box";
public static final String RADIO_WIDGETTYPE = "radio button";
public static final String TEXTBOX_WIDGETTYPE = "text box";
public static final String TEXT_WIDGETTYPE = "text";
public static final String PULLDOWNLIST_WIDGETTYPE = "pull-down list";
public static final String PULLDOWNITEM_WIDGETTYPE = "pull-down item";
public static final String LISTBOXITEM_WIDGETTYPE = "list box item";
public static final String CONTEXTMENU_WIDGETTYPE = "context menu";
public static final String MENUHEADER_WIDGETTYPE = "menu";
public static final String SUBMENU_WIDGETTYPE = "submenu";
public static final String MENUITEM_WIDGETTYPE = "menu item";
public static final String GRAFFITI_WIDGETTYPE = "graffiti";
public static final String NONINTERACTIVE_WIDGETTYPE = "non-interactive";
public static final String X_ATTR = "x";
public static final String Y_ATTR = "y";
public static final String WIDTH_ATTR = "width";
public static final String HEIGHT_ATTR = "height";
public static final String BWIDTH_ATTR = "w";
public static final String BHEIGHT_ATTR = "h";
public static final String MEASURED_WIDTH_ATTR = "measuredW";
public static final String MEASURED_HEIGHT_ATTR = "measuredH";
public static final String FINAL_FRAME_NAME = "FINAL FRAME";
protected Map<String, SimpleWidgetGroup> groupRegistry =
new HashMap<String, SimpleWidgetGroup>();
public String designPathName = null;
public String designName = "";
public Design design = null;
public HashSet<String> visitedFramesNames = new HashSet<String>();
public ArrayList<Frame> visitedFrames = new ArrayList<Frame>();
//TODO: handle this widget loader
public static ObjectLoader.IObjectLoader<Widget> widgetLoader =
Widget.getImportLoader2();
protected static ObjectLoader.IObjectLoader<Frame> frameLoader =
Frame.getImportLoader();
/** Returns a String consisting of the name of this converter file,
* "Balsamiq API Mockups".
* The name of the converter is used in the "Import... ->" sub-menu
*
* @return the name of the converter file
*/
public String name()
{
return "Balsamiq API Mockups";
}
/** Returns true if the input from the user should be directory and returns
* false if the input from the user should be a file.
* Note that in Java, a File object can represent a directory or a file.
*
* When importing from Balsamiq Mockups, a directory consisting of all of
* the .bmml files must be given.
*
* @return the boolean value of weather the inputFile should be a file or
* directory
*/
public boolean isInputFileDirectory()
{
return true;
}
/** Returns a String array consisting of all of the valid extensions to be
* imported using this converter file.
* The allowedExtensions method is used whether a file or a directory is
* given as input Balsamiq Mockups files are saved as a .bmml file or a
* .xml file
*
* No problem will occur if a specified extension is duplicated in the
* array.
*
* @return the valid extensions of files to be imported using this
* converter file
*/
@Override
public String[] allowedExtensions()
{
String[] exts = new String[]{"xml", "bmml", null, ""};
return exts;
}
/** The new design is created and added to the project window.
* This method must be implemented by the user.
*
* @param inputFile the specified directory or file
* @param design The newly created design. The design is passed to
* the converter file so that frames may be added to it.
*
* @see the new design in the project window.
*/
public void importDesign(File inputFile, Design newDesign)
{
this.design = newDesign;
designName = this.design.getName();
designPathName = inputFile.getParent();
try {
parseBalsamiqFile(inputFile);
int minFrameWidth =
DesignUtil.getFrameMinWidth();
int minFrameHeight =
DesignUtil.getFrameMinHeight();
double frameScale =
DesignUtil.getFrameScaleFactor();
/*The frame situator spaces out the frames in the design window */
DesignUtil.IFrameSituator frameSituator =
new DesignUtil.ByRowFrameSituator(0.0,
0.0,
16.0,
16.0,
minFrameWidth,
minFrameHeight,
CogToolPrefs.getFramesPerRow(),
frameScale);
//Iterate over the list of frames and add each one to the design window.
//A data structure (such as an ArrayList) that stores elements in the order that
//they were inserted is needed in order to keep a consistent behavior
for(Frame frame: visitedFrames){
frameSituator.situateNextFrame(frame);
}
} catch (IOException e) {
throw new RcvrImportException("importDesign() has encountered an exception " + e);
}
}
/** Helper function used by importDesign.
* This method is used to parse the tags of each .bmml file in the
* directory. The file represents a {@link File} in the directory.
*
* @param inputFile the specified directory or file
* @see the new design in the project window.
*/
public void parseBalsamiqFile(File inputFile) throws IOException
{
String fileName = inputFile.getName();
int lastIndex = fileName.lastIndexOf(".");
fileName = (lastIndex == -1) ? fileName
: fileName.substring(0, lastIndex);
/* Check to make sure that this frame has not been created yet. Cycles
* by transitions may exist in the design and these files do not need
* to be parsed more than once. */
if(! visitedFramesNames.contains(fileName))
{
visitedFramesNames.add(fileName);
// Create a Xerces DOM Parser. A DOM Parser can parse an XML file
// and create a tree representation of it. This same parser method
// is used in ImportCogTool.java to import from CogTool XML.
DOMParser parser = new DOMParser();
InputStream fis = new FileInputStream(inputFile);
Reader input = new InputStreamReader(fis, "UTF-8");
// Parse the Document and traverse the DOM
try {
parser.parse(new InputSource(input));
} catch (SAXException e) {
throw new RcvrImportException("Not a valid XML file to parse.");
}
//Traverse the xml tags in the tree beginning at the root, the document
Document document = parser.getDocument();
parseFrame(document, fileName);
}
}
/** Helper function used by importDesign.
* This method is used to return the corresponding Frame object for a
* given frame name
*
* @param frameName the specified frame name
*/
protected Frame getFrame(String frameName)
{
if ((frameName != null) &&
! frameName.equals("") &&
! frameName.equals(FINAL_FRAME_NAME))
{
// Fail-fast -- right now, we have only one implementation of
// Frame, so this cannot fail.
Frame frame = design.getFrame(frameName);
if (frame == null) {
frame = frameLoader.createObject();
frame.setName(frameName);
design.addFrame(frame);
visitedFrames.add(frame);
}
return frame;
}
return null;
}
/** Helper function used by importDesign.
* This method is used to parse the tags of each .bmml file and assign the
* attributes of the frame to the frame object
*
* @param document the root of the XML tree
* @param fileName the specified directory or file
* @return the newly created frame
*/
protected Frame parseFrame(Node document, String fileName) throws IOException
{
// This adds the created frame to the design
Frame frame = getFrame(fileName);
if(frame == null){
throw new RcvrImportException("Null Frame.");
}
//addAttributes(frame, node);
Frame.setFrameDevices(frame, design.getDeviceTypes());
NodeList children = document.getChildNodes();
double frameWidth = 0;
double frameHeight = 0;
//TODO: setting variables but never reading them
if (children != null) {
for (int i = 0; i < children.getLength(); i++) {
Node child = children.item(i);
if (child.getNodeName().equalsIgnoreCase(MOCKUP_ELT)) {
frameWidth =
Integer.parseInt(getAttributeValue(child,
MEASURED_WIDTH_ATTR));
frameHeight =
Integer.parseInt(getAttributeValue(child,
MEASURED_HEIGHT_ATTR));
}
}
}
//TODO: if it doesnt import anything, a new frame window should still be imported?
parseWidgets(document, frame);
return frame;
}
/** Helper function used by importDesign.
* This method is used to parse the tags of each .bmml file in the directory
*
* @param document the root of the XML tree
* @param fileName the specified directory or file
* @return the newly created frame
*/
protected void parseWidgets(Node node, Frame frame)
throws IOException
{
NodeList children = node.getChildNodes();
if (node.getNodeName().equalsIgnoreCase(CONTROLS_ELT)) {
System.out.println("controls " + children.getLength());
/* The controls xml tag will have a list of children which are control xml tags.
* A Balsamiq control is similar to a CogTool widget */
for (int i = 0; i < children.getLength(); i++) {
Node controlTag = children.item(i);
String nodeName = controlTag.getNodeName();
if (nodeName.equalsIgnoreCase(CONTROL_ELT)) {
String balsamiqControlType = getAttributeValue(controlTag, CONTROL_TYPE_ATTR);
String widgetTypeString = (balsamiqControlType == null ) ? null : getBMMLWidgetType(balsamiqControlType);
System.out.println("316-widgetTypeString " + widgetTypeString);
if(widgetTypeString.equals("group")){
System.out.println("319-calling bmmlgroup");
parseBMMLGroup(controlTag, frame, null, 0.0, 0.0);
}
else{
Widget widget = parseBMMLWidget(controlTag, frame, null, 0.0, 0.0);
if(widget != null){
frame.addWidget(widget);
}
}
}
}
}
else {
for (int i = 0; i < children.getLength(); i++) {
parseWidgets(children.item(i), frame);
}
}
}
/** Helper function used by importDesign.
* This method is used to parse the tags of each .bmml file in the directory
*
* @param document the root of the XML tree
* @param fileName the specified directory or file
* @return the newly created frame
*/
protected WidgetType getWidgetType(String widgetType)
{
if ((widgetType == null) || // TODO: note error, return null on this case?
widgetType.equalsIgnoreCase(BUTTON_WIDGETTYPE))
{
return WidgetType.Button;
}
if (widgetType.equalsIgnoreCase(LINK_WIDGETTYPE)) {
return WidgetType.Link;
}
if (widgetType.equalsIgnoreCase(CHECKBOX_WIDGETTYPE)) {
return WidgetType.Check;
}
if (widgetType.equalsIgnoreCase(RADIO_WIDGETTYPE)) {
return WidgetType.Radio;
}
if (widgetType.equalsIgnoreCase(TEXTBOX_WIDGETTYPE)) {
return WidgetType.TextBox;
}
if (widgetType.equalsIgnoreCase(TEXT_WIDGETTYPE)) {
return WidgetType.Text;
}
if (widgetType.equalsIgnoreCase(PULLDOWNLIST_WIDGETTYPE)) {
return WidgetType.PullDownList;
}
if (widgetType.equalsIgnoreCase(PULLDOWNITEM_WIDGETTYPE)) {
return WidgetType.PullDownItem;
}
if (widgetType.equalsIgnoreCase(LISTBOXITEM_WIDGETTYPE)) {
return WidgetType.ListBoxItem;
}
if (widgetType.equalsIgnoreCase(CONTEXTMENU_WIDGETTYPE)) {
return WidgetType.ContextMenu;
}
if (widgetType.equalsIgnoreCase(MENUHEADER_WIDGETTYPE)) {
return WidgetType.Menu;
}
if (widgetType.equalsIgnoreCase(SUBMENU_WIDGETTYPE)) {
return WidgetType.Submenu;
}
if (widgetType.equalsIgnoreCase(MENUITEM_WIDGETTYPE)) {
return WidgetType.MenuItem;
}
if (widgetType.equalsIgnoreCase(GRAFFITI_WIDGETTYPE)) {
return WidgetType.Graffiti;
}
if (widgetType.equalsIgnoreCase(NONINTERACTIVE_WIDGETTYPE)) {
return WidgetType.Noninteractive;
}
return null;
}
/** Helper function used by importDesign.
* This method is used to parse the tags of each .bmml file in the directory
*
* @param document the root of the XML tree
* @param fileName the specified directory or file
* @return the newly created frame
*/
protected String getBMMLWidgetType(String widgetType)
{
if ((widgetType == null)||
widgetType.equalsIgnoreCase("com.balsamiq.mockups::Button"))
{
return BUTTON_WIDGETTYPE;
}
if (widgetType.equalsIgnoreCase("com.balsamiq.mockups::Link")) {
return LINK_WIDGETTYPE;
}
if (widgetType.equalsIgnoreCase("com.balsamiq.mockups::CheckBox")) {
return CHECKBOX_WIDGETTYPE;
}
if (widgetType.equalsIgnoreCase("com.balsamiq.mockups::RadioButton")) {
return RADIO_WIDGETTYPE;
}
if (widgetType.equalsIgnoreCase("com.balsamiq.mockups::TextBox")) {
return "text";
}
if (widgetType.equalsIgnoreCase("com.balsamiq.mockups::Breadcrumbs")) {
return "breadcrumbs";
}
if (widgetType.equalsIgnoreCase("__group__")) {
return "group";
}
if (widgetType.equalsIgnoreCase("com.balsamiq.mockups::LinkBar")) {
return "linkbar";
}
return null;
}
/** Helper function used by parseFrame
* This method is used to parse the tags of each control tag in Balsamiq XML. A control tag
* represents a Balsamiq Widget.
*
* @param node a node of the tree that represents a Balsamiq widget
* @frame frame that the widget is being added to
* @group parent group
* @groupX x coordinate of parent group
* @groupY y coordinate of parent group
* @return the newly created widget
*/
protected Widget parseBMMLWidget(Node node, Frame frame, SimpleWidgetGroup group, double groupX, double groupY) throws IOException
{
System.out.println("parseBMMLWidget " + group + " x: " + groupX + "y: " + groupY);
NodeList children = node.getChildNodes();
Widget widget = null;
WidgetType widgetType = null;
String balsamiqControlType = getAttributeValue(node, CONTROL_TYPE_ATTR);
String widgetTypeString = (balsamiqControlType == null ) ? null : getBMMLWidgetType(balsamiqControlType);
String widgetName = getAttributeValue(node, CONTROL_ID_ATTR);
if(widgetName == null){
//TODO: make a random widget name and move on. report to the user. need to be unique within the frame. Frame.java has a method for this
}
System.out.println("462- wN " + widgetName + "widT " + widgetTypeString);
if(widgetTypeString == null){
//TODO: report to the user
}
else
{
widgetType = getWidgetType(widgetTypeString);
//Parse the widget name, location and size from the attributes of the XML tag
//TODO: Error check all of the getAttributeValues()
double x =
Double.parseDouble(getAttributeValue(node, X_ATTR));
double y =
Double.parseDouble(getAttributeValue(node, Y_ATTR));
//TODO: difference between w and measuredW, same for height
double width =
Integer.parseInt(getAttributeValue(node,
BWIDTH_ATTR));
double height =
Integer.parseInt(getAttributeValue(node,
BHEIGHT_ATTR));
double measWidth =
Integer.parseInt(getAttributeValue(node,
MEASURED_WIDTH_ATTR));
double measHeight =
Integer.parseInt(getAttributeValue(node,
MEASURED_HEIGHT_ATTR));
if(width == -1 && height == -1) //TODO: make sure the dimensions are positive
{
System.out.println("493- changing widget dimensions");
width = measWidth;
height = measHeight;
}
/*bounds is the size and location of the widget*/
if(group != null)
{
System.out.println("488-Group is not null");
DoubleRectangle rect = group.getGroupBounds();
if(rect != null)
{
System.out.println("500-rect is not null");
//x += groupX;
//y += groupY;
}
}
x += groupX;
y += groupY;
System.out.println("new widget has dimens:" + x + " " + y);
DoubleRectangle bounds = new DoubleRectangle(x,y, width, height);
if(widgetType == WidgetType.Check)
{
System.out.println("505-The widget is a check so now make a group for it");
if(group == null){
group = new GridButtonGroup();
group.setName("newName");//give it a random widget name
groupRegistry.put("newName", group);
}
//if(group instanceof GridButtonGroup)
//{
int eltCount = group.elementCount();
System.out.println("513-group is gridbuttongroup " + group.getName() + " " + eltCount);
if(eltCount > 0)
{
//TODO: check if the widgets align best horizontally or vertically
//align the widgets to average coord, first, left or rightmost
IWidget wid = group.getElement(group.elementCount()-1);
DoubleRectangle bounds2 = wid.getEltBounds();
System.out.println("530 " + bounds2.getX()+ " " + bounds2.getWidth() + " " + bounds2.getY()+ " " + bounds2.getHeight());
double diffX = Math.abs(x- bounds2.getX());
double diffY = Math.abs(y- bounds2.getY());
System.out.println("diffX " + diffX + " " + diffY);
DoubleRectangle bounds3 = new DoubleRectangle(bounds2.getX()+ bounds2.getWidth(),bounds2.getY(), width, height);
if(diffX < diffY){
bounds3 = new DoubleRectangle(bounds2.getX(),bounds2.getY()+ bounds2.getHeight(), width, height);
}
widget = new CheckBox((GridButtonGroup)group, bounds3, widgetName);
}
else
{
widget = new CheckBox((GridButtonGroup)group, bounds, widgetName);
}
widget.setWidgetType(widgetType);
/*}
else
{
System.out.println("517-group2 is gridbuttongroup");
GridButtonGroup group2 = new GridButtonGroup();
group2.setName("newNameCheck");
groupRegistry.put("newNameCheck", group2);
widget = new CheckBox(group2, bounds, widgetName);
//group.addElement((IWidget) group2);
}*/
}
else
{
widget = new Widget(bounds, widgetType);
}
widget.setName(widgetName);
/*AShape widgetShape = new ShapeRectangle(bounds);
widget.setShape(widgetShape);*/
for (int i = 0; i < children.getLength(); i++) {
Node child = children.item(i);
String nodeName = child.getNodeName();
/*Whitespace in the DOM tree is represented as #text. Ignore these nodes. Anywhere between the open and closed tag*/
if(! nodeName.equals("#text"))
{
if(nodeName.equalsIgnoreCase(CONTROL_PROP_ELT))
{
NodeList controlTags = child.getChildNodes();
for (int j = 0; j < controlTags.getLength(); j++) {
Node controlTag = controlTags.item(j);
String propertyTag = controlTag.getNodeName();
//CogTool widget Display Label
if (propertyTag.equalsIgnoreCase(BALSAMIQ_TEXT_ELT)) {
/*Must decode the title. For instance the title in
Balsamiq, "First%20Frame" is "First Frame" in CogTool*/
String title = getElementText(controlTag);
title = URLDecoder.decode(title, "UTF-8");
widget.setTitle(title);
}
//CogTool transition on the present widget
else if (propertyTag.equalsIgnoreCase(HREF_ELT)) {
String destinationFileName = getElementText(controlTag);
File destinationFile = new File(designPathName, destinationFileName);
//Make sure the file exists before attempting to parse the file
if(destinationFile.exists()){
parseBalsamiqFile(destinationFile);
parseTransition(destinationFileName, widget);
}
}// END OF TRANSITION
else if (propertyTag.equalsIgnoreCase(STATE_ELT)) {
//TODO: work on states
//A Balsamiq Button can be normal, selected, or disabled
String state = getElementText(controlTag);
if(state.equals("selected")){
widget.setAttribute(WidgetAttributes.IS_SELECTED_ATTR, Boolean.TRUE);
}
else
{
//TODO: Other Balsamiq states like "disabled", "disabled and selected"
}
}// END OF STATE OF BUTTON
}// END OF PROPERTY TAG
}// END OF CONTROL PROPERTIES TAG
}
}
}
widget.setRendered(true);
return widget;
} // parseBMMLWidget
//TODO: probably need to give the group dimensions
/** Helper function used by parseFrame
* This method is used to parse the tags of each control tag in Balsamiq XML. A control tag
* represents a Balsamiq Group.
*
* @param node a node of the tree that represents a Balsamiq widget
* @frame frame that the widget is being added to
* @group parent group
* @groupX x coordinate of parent group
* @groupY y coordinate of parent group
* @return new group containing widgets
*/
public void parseBMMLGroup(Node groupXMLtag, Frame frame, SimpleWidgetGroup parentGroup, double initX, double initY)
{
SimpleWidgetGroup widgetGrp = null;
NodeList groupSubtags = groupXMLtag.getChildNodes();
String grpName = getAttributeValue(groupXMLtag, CONTROL_ID_ATTR);
double x =
Double.parseDouble(getAttributeValue(groupXMLtag, X_ATTR));
double y =
Double.parseDouble(getAttributeValue(groupXMLtag, Y_ATTR));
x += initX;
y += initY;
if(grpName == null){
//TODO: report to the user
}
System.out.println("587-GROUP " + grpName);
if ((grpName != null) && ! "".equals(grpName)) {
widgetGrp = groupRegistry.get(grpName);
for (int i = 0; i < groupSubtags.getLength(); i++) {
Node groupSubtag = groupSubtags.item(i);
String groupSubtagName = groupSubtag.getNodeName();
System.out.println("598-nodeName1 " + groupSubtagName);
/*Whitespace in the DOM tree is represented as #text. Ignore these nodes*/
if(! groupSubtagName.equals("#text"))
{
if(groupSubtagName.equalsIgnoreCase("groupChildrenDescriptors"))
{
System.out.println("605-groupchildrendescriptors");
WidgetType groupType = checkWidgetGroupType(groupSubtag);
if(groupType != null){
System.out.println("630-groupwidgetType " + groupType.getName());
}
else{
System.out.println("groupwidgetType is null");
}
NodeList children2 = groupSubtag.getChildNodes();
for (int j = 0; j < children2.getLength(); j++) {
Node child3 = children2.item(j);
String nodeName3 = child3.getNodeName();
System.out.println("657- nodeName3 " + nodeName3 + " j is " + j + " length " + children2.getLength());
if (nodeName3.equalsIgnoreCase(CONTROL_ELT)) {
System.out.println("660-This is a control!");
Widget widget2 = null;
if (widgetGrp == null) {
if (groupType == WidgetType.Radio) {
widgetGrp = new RadioButtonGroup();
}
else if(groupType == WidgetType.Check){
System.out.println("654-grouptype is a check");
widgetGrp = new GridButtonGroup();
}
else{
widgetGrp = new SimpleWidgetGroup(1);
}
widgetGrp.setName(grpName);
groupRegistry.put(grpName, widgetGrp);
}
try {
String balsamiqControlType = getAttributeValue(child3, CONTROL_TYPE_ATTR);
String widgetTypeString = (balsamiqControlType == null ) ? null : getBMMLWidgetType(balsamiqControlType);
System.out.println("663-widgetTypeString " + widgetTypeString);
if(widgetTypeString.equals("group")){
System.out.println("682-calling bmmlgroup");
parseBMMLGroup(child3, frame, widgetGrp, x, y);
}
widget2 = parseBMMLWidget(child3, frame, widgetGrp, x,y);
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
int widgetCount = widgetGrp.elementCount();
System.out.println("692- WIDGET COUNT for " + grpName + ": " + widgetCount + " " + widget2.getName());
if(widget2 != null)
{
//TODO: check widget does not need to be added here
//widgetGrp.addElement(widget2);
widget2.setParentGroup(widgetGrp);
//frame.addEltGroup(gridWidgetGrp);
frame.addWidget(widget2);
widgetLoader.set(widget2, Widget.widgetTypeVAR, widget2.getWidgetType());
//AShape widgetShape = widget2.getShapeType();
//if (widgetShape != null) {
// widgetLoader.set(widget2, Widget.shapeVAR, widgetShape);
// }
}
System.out.println("696-widget2 added");
}
}
}
}
}
}
}
//TODO: check if the widgets align best horizontally or vertically
//align the widgets to average coord, first, left or rightmost
//prohibit the dimensions at the api level
/** Helper function used by parseWidget
* This method is used to verify that all widgets in a group are of the same type
*
* @param groupXMLNode the node that represents a group
*/
public WidgetType checkWidgetGroupType(Node groupXMLNode)
{
NodeList children = groupXMLNode.getChildNodes();
String balsamiqGroupControlType = null;
/*boolean xCoordAlign = true;
boolean yCoordAlign = true;
double initialX = 0;
double initialY = 0;*/
for (int j = 0; j < children.getLength(); j++) {
Node child = children.item(j);
String nodeName = child.getNodeName();
String balsamiqControlType = getAttributeValue(child, CONTROL_TYPE_ATTR);
/*double x =
Double.parseDouble(getAttributeValue(child, X_ATTR));
double y =
Double.parseDouble(getAttributeValue(child, Y_ATTR));*/
System.out.println("713 - WidgetGroupType- nodeName " + nodeName + " " + balsamiqControlType);
if( balsamiqGroupControlType != null && balsamiqControlType != null && ! balsamiqGroupControlType.equals( balsamiqControlType))
{
return null;
}
else if(balsamiqControlType != null)
{
balsamiqGroupControlType = balsamiqControlType;
}
/*if(j==0){
initialX = x;
initialY = y;
}
else
{
if(x != initialX){
xCoordAlign = false;
}
if(y != initialY){
yCoordAlign = false;
}
}*/
}
String widgetTypeString = (balsamiqGroupControlType == null ) ? null : getBMMLWidgetType(balsamiqGroupControlType);
System.out.println("724 " + widgetTypeString);
/*if(xCoordAlign || yCoordAlign)
{*/
return getWidgetType(widgetTypeString);
/*}
else
{
return null;
}*/
}
/** Helper function used by parseWidget
* This method is used to parse the href tag that is a link from a widget
* to another frame
*
* @param destinationFrameName the name of the frame that the transition
* is going to
* @param widget the widget that the transition starts from
*/
protected void parseTransition(String destFrameFileName, Widget widget)
{
String frameName = (destFrameFileName.lastIndexOf(".") == -1)? destFrameFileName:
destFrameFileName.substring(0, destFrameFileName.lastIndexOf('.'));
/* getFrame() returns the frame for the specified string. If the string is null then
a null frame is returned. */
Frame destinationFrame = (frameName != null) ? getFrame( frameName) : null;
if(destinationFrame != null)
{
//Default Mouse Click Transition
//TODO: MAKE SURE THE MOUSE IS A DEVICE. check which devices are allowed and then make an action from there
AAction action = new ButtonAction(MouseButtonState.Left, MousePressType.Click, AAction.NONE);
//Create the transition and add it to the widget
Transition t = new Transition(widget, destinationFrame, action);
if (t != null) {
widget.addTransition(t);
}
}
} // parseTransition
/** Helper function used by importDesign to retrieve attributes for
* frames and widgets. This method is used to return the value of a
* specific attribute associated with an xml tag
*
* @param node a node representing an xml tag in the XML tree
* @param attr String attribute associated with this tag
* @return the value associated with this attribute at this node
*/
protected String getAttributeValue(Node node, String attr)
{
NamedNodeMap attributes = node.getAttributes();
if (attributes != null) {
Node attributeNode = attributes.getNamedItem(attr);
if (attributeNode != null) {
return attributeNode.getNodeValue();
}
}
return null;
}
/** Helper function used by importDesign to retrieve attributes for
* frames and widgets. This method is used to return the value of a
* specific attribute associated with an xml tag
*
* @param node a node representing an xml tag in the XML tree
* @param attr String attribute associated with this tag
* @return the value associated with this attribute at this node
*/
protected String getElementText(Node node)
{
if (node.getFirstChild() != null) {
return node.getFirstChild().getNodeValue().trim();
}
return "";
}
/** Returns a set consisting of all of the selected devices that should be
* used for this newly imported design. These devices cannot be de-selected
* by the UI designer.
*
* @return selected devices as specified by the programmer
*/
@Override
public Set<DeviceType> selectedDevices()
{
HashSet<DeviceType> types = new HashSet<DeviceType>();
types.add(DeviceType.Mouse);
types.add(DeviceType.Keyboard);
return types;
}
}