1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30 package org.mlc.swing.layout;
31
32 import com.jgoodies.forms.layout.CellConstraints;
33 import java.awt.Component;
34 import java.awt.Container;
35 import java.awt.Insets;
36 import java.beans.Statement;
37 import java.beans.XMLDecoder;
38 import java.beans.XMLEncoder;
39 import java.io.ByteArrayInputStream;
40 import java.io.ByteArrayOutputStream;
41 import java.io.InputStream;
42 import java.util.*;
43 import javax.xml.parsers.DocumentBuilder;
44 import javax.xml.parsers.DocumentBuilderFactory;
45 import javax.xml.transform.OutputKeys;
46 import javax.xml.transform.Transformer;
47 import javax.xml.transform.TransformerFactory;
48 import javax.xml.transform.dom.DOMSource;
49 import javax.xml.transform.stream.StreamResult;
50 import org.w3c.dom.Document;
51 import org.w3c.dom.NamedNodeMap;
52 import org.w3c.dom.Node;
53 import org.w3c.dom.NodeList;
54
55 import javax.swing.*;
56
57 /***
58 * This class handles the serialization and deserialization of the xml files
59 * that we are using to store the layout constraints.
60 *
61 * <p>In the consuming program, the use of this class might look like:
62 <br><code>
63 InputStream constraints = this.getClass().getResourceAsStream(xmlFile);
64 LayoutConstraintsManager layoutConstraintsManager =
65 LayoutConstraintsManager.getLayoutConstraintsManager(constraints);
66 LayoutManager layout = layoutConstraintsManager.createLayout("panel", this);
67 this.setLayout(layout);
68 </code>
69 *
70 * <p>[I'm sure there are more
71 * elegant ways of handling this (like JAXB) or some other mapping software but
72 * this is simple, it works, and we don't have to package a bunch of other
73 * software or files.]
74 *
75 * @author Michael Connor
76 */
77 public class LayoutConstraintsManager
78 {
79 String defaultColumnSpecs = "right:max(30dlu;pref),3dlu,80dlu,10dlu,right:max(30dlu;pref),3dlu,80dlu,1dlu:grow";
80
81 String defaultRowSpecs = "pref,3dlu,pref,3dlu,pref,3dlu,pref,3dlu,pref,3dlu,pref,3dlu,pref,3dlu,pref,3dlu,pref,3dlu,pref,3dlu,pref:grow";
82
83 static Set<Class> textComponents = new HashSet<Class>();
84
85 static
86 {
87 textComponents.add(JButton.class);
88 textComponents.add(JCheckBox.class);
89 textComponents.add(JRadioButton.class);
90 textComponents.add(JToggleButton.class);
91 textComponents.add(JLabel.class);
92 }
93
94 Map<ContainerLayout, Container> containers = new HashMap<ContainerLayout, Container>();
95
96 List<ContainerLayout> layouts = new ArrayList<ContainerLayout>();
97
98 /***
99 * This method will create a LayoutConstraintsManager with default JGoodies
100 * row and column specs that are common in applications. The user can then
101 * manipulate these default specs using the LayoutFrame to fine tune the specs
102 * to be whatever they want.
103 */
104 public LayoutConstraintsManager()
105 {
106 }
107
108 /***
109 * This method will create a LayoutConstraintsManager with the JGoodies specs
110 * provided as default
111 */
112 public LayoutConstraintsManager(String defaultColumnSpecs,
113 String defaultRowSpecs)
114 {
115 this.defaultColumnSpecs = defaultColumnSpecs;
116 this.defaultRowSpecs = defaultRowSpecs;
117 }
118
119 public List<ContainerLayout> getLayouts()
120 {
121 List<ContainerLayout> list = new ArrayList<ContainerLayout>(layouts.size());
122 list.addAll(layouts);
123 return list;
124 }
125
126 /***
127 * This method will build a layout from the xml file based on the name and
128 * call setLayout on the container passed in.
129 */
130 public void setLayout(String name, Container container)
131 {
132 ContainerLayout containerLayout = getLayout(name);
133
134 if (containerLayout == null)
135 {
136 containerLayout = new ContainerLayout(name, defaultColumnSpecs,
137 defaultRowSpecs);
138 layouts.add(containerLayout);
139 }
140
141
142
143 container.setLayout(containerLayout);
144 containers.put(containerLayout, container);
145 }
146
147 /***
148 * This method creates a layout by first trying to look in memory to see if a
149 * layout has been defined with the given name. If a layout exists, it is
150 * returned. Note that when I say in memory that probably means that it was
151 * defined in the xml file. If one doesn't exist, a layout with what I
152 * consider relatively generic constraints will be created and returned. The
153 * reason this method requires the container is because in the case where you
154 * are trying to layout the container visually, I need to be able to get a
155 * handle on the container so I can make calls to add components to it during
156 * interactive layout. This will not be touched at runtime if you are not
157 * doing anything interactively. This method should be seen as a replacement
158 * for LayoutConstraintsManager.setLayout(String name, Container container) as
159 * it's more natural to set the layout on the container yourself.
160 */
161 public ContainerLayout createLayout(String name, Container container)
162 {
163 ContainerLayout containerLayout = getLayout(name);
164
165 if (containerLayout == null)
166 {
167 containerLayout = new ContainerLayout(name, defaultColumnSpecs,
168 defaultRowSpecs);
169 layouts.add(containerLayout);
170 }
171
172 containers.put(containerLayout, container);
173 return containerLayout;
174 }
175
176 public Container getContainer(ContainerLayout layout)
177 {
178 return containers.get(layout);
179 }
180
181 private ContainerLayout getLayout(String name)
182 {
183 for (int i = 0; i < layouts.size(); i++)
184 if (layouts.get(i).getName().equals(name))
185 return layouts.get(i);
186
187 return null;
188 }
189
190
191
192
193
194
195
196
197
198 public ContainerLayout getContainerLayout(String containerName)
199 {
200 ContainerLayout layout = getLayout(containerName);
201 return layout;
202 }
203
204 public void removeLayout(ContainerLayout containerLayout)
205 {
206 layouts.remove(containerLayout);
207 }
208
209 public void addLayout(ContainerLayout containerLayout)
210 {
211 layouts.add(containerLayout);
212 }
213
214 /***
215 Get an XML representation of the FormLayout constraints for all containers
216 in this manager.
217 */
218 public String getXML()
219 {
220 StringBuffer xml = new StringBuffer(
221 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n\n");
222 xml.append("<containers>\n");
223
224 for (int index = 0; index < layouts.size(); index++)
225 {
226 ContainerLayout layout = layouts.get(index);
227 LinkedHashMap<String, CellConstraints> constraintMap = layout
228 .getCellConstraints();
229
230 xml.append(" <container name=\"" + layout.getName() + "\"\n");
231 xml.append(" columnSpecs=\""
232 + layout.getColumnSpecsString() + "\"\n");
233 xml.append(" rowSpecs=\"" + layout.getRowSpecsString()
234 + "\">\n");
235
236 for (Iterator componentNames = constraintMap.keySet().iterator(); componentNames
237 .hasNext();)
238 {
239 String componentName = (String) componentNames.next();
240 CellConstraints constraints = constraintMap.get(componentName);
241 xml.append(" <cellconstraints ");
242 xml.append("name=\"" + componentName + "\" ");
243 xml.append("gridX=\"" + constraints.gridX + "\" ");
244 xml.append("gridY=\"" + constraints.gridY + "\" ");
245 xml.append("gridWidth=\"" + constraints.gridWidth + "\" ");
246 xml.append("gridHeight=\"" + constraints.gridHeight + "\" ");
247 xml.append("horizontalAlignment=\"" + getAlignment(constraints.hAlign)
248 + "\" ");
249 xml.append("verticalAlignment=\"" + getAlignment(constraints.vAlign)
250 + "\" ");
251 xml.append("topInset=\"" + constraints.insets.top + "\" ");
252 xml.append("bottomInset=\"" + constraints.insets.bottom + "\" ");
253 xml.append("rightInset=\"" + constraints.insets.right + "\" ");
254 xml.append("leftInset=\"" + constraints.insets.left + "\"/>\n");
255 }
256
257 for (Iterator componentNames = constraintMap.keySet().iterator(); componentNames
258 .hasNext();)
259 {
260 String componentName = (String) componentNames.next();
261 Component component = layout.getComponentByName(componentName);
262
263 if (component != null)
264 {
265 Map<String, Object> customProperties = layout
266 .getCustomProperties(componentName);
267
268 boolean hasProperties = false;
269 boolean isTextComponent = isTextComponent(component);
270
271
272
273
274 for (String propertyName : customProperties.keySet())
275 {
276 if ((!isTextComponent) || (!propertyName.equals("text")))
277 {
278 hasProperties = true;
279 break;
280 }
281 }
282
283 if (hasProperties)
284 {
285 xml.append("\n <properties component=\"" + componentName
286 + "\">\n");
287 for (String propertyName : customProperties.keySet())
288 {
289
290 if (isTextComponent && propertyName.equals("text"))
291 break;
292
293 ByteArrayOutputStream stream = new ByteArrayOutputStream();
294 XMLEncoder xmlEncoder = new XMLEncoder(stream);
295 xmlEncoder.setOwner(component);
296
297 String methodName = "set"
298 + propertyName.substring(0, 1).toUpperCase()
299 + (propertyName.length() > 1 ? propertyName.substring(1) : "");
300 xmlEncoder.writeStatement(new Statement(xmlEncoder, methodName,
301 new Object[] { customProperties.get(propertyName) }));
302 xmlEncoder.close();
303
304 String propertyXml = new String(stream.toByteArray());
305 int voidStart = propertyXml.indexOf("<void");
306 int voidEnd = propertyXml.indexOf(">", voidStart);
307 int end = propertyXml.lastIndexOf("</void>");
308 String xmlWithoutDec = propertyXml.substring(voidEnd + 1, end).trim();
309 String indented = " "
310 + xmlWithoutDec.replaceAll("\n", "\n ");
311 xml.append(" <property name=\"" + propertyName + "\">");
312 xml.append(indented);
313 xml.append("</property>\n");
314 }
315
316 xml.append(" </properties>\n");
317 }
318 }
319 }
320
321 xml.append(" </container>\n");
322 }
323
324 xml.append("</containers>\n");
325 return xml.toString();
326 }
327
328 public static boolean isTextComponent(Component component)
329 {
330 for (Class clazz : textComponents)
331 {
332 if (clazz.isAssignableFrom(component.getClass()))
333 return true;
334 }
335 return false;
336 }
337
338 private static String createString(NodeList childNodes)
339 {
340
341 try
342 {
343 ByteArrayOutputStream byteStream = new ByteArrayOutputStream();
344 TransformerFactory tFactory = TransformerFactory.newInstance();
345 Transformer transformer = tFactory.newTransformer();
346 transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes");
347
348 for (int i = 0; i < childNodes.getLength(); i++)
349 {
350 Node child = childNodes.item(i);
351 DOMSource source = new DOMSource(child);
352 StreamResult result = new StreamResult(byteStream);
353 transformer.transform(source, result);
354 }
355 return new String(byteStream.toByteArray());
356 }
357 catch (Exception e)
358 {
359 e.printStackTrace();
360 throw new RuntimeException("unexpected error", e);
361 }
362 }
363
364 public static final String DEFAULT = "default";
365
366 public static final String FILL = "fill";
367
368 public static final String CENTER = "center";
369
370 public static final String LEFT = "left";
371
372 public static final String RIGHT = "right";
373
374 public static final String TOP = "top";
375
376 public static final String BOTTOM = "bottom";
377
378 /***
379 * Translates an alignment value to a string.
380 */
381 public static String getAlignment(CellConstraints.Alignment alignment)
382 {
383 String value = null;
384
385 if (alignment == CellConstraints.DEFAULT)
386 value = DEFAULT;
387 else if (alignment == CellConstraints.FILL)
388 value = FILL;
389 else if (alignment == CellConstraints.CENTER)
390 value = CENTER;
391 else if (alignment == CellConstraints.LEFT)
392 value = LEFT;
393 else if (alignment == CellConstraints.RIGHT)
394 value = RIGHT;
395 else if (alignment == CellConstraints.TOP)
396 value = TOP;
397 else if (alignment == CellConstraints.BOTTOM)
398 value = BOTTOM;
399
400 if (value == null)
401 throw new RuntimeException("Unknown alignment type");
402 else
403 return value;
404 }
405
406 /***
407 * Translates a string to an alignment value.
408 */
409 public static CellConstraints.Alignment getAlignment(String value)
410 {
411 CellConstraints.Alignment alignment = null;
412
413 if (value.equalsIgnoreCase(DEFAULT))
414 alignment = CellConstraints.DEFAULT;
415 else if (value.equalsIgnoreCase(FILL))
416 alignment = CellConstraints.FILL;
417 else if (value.equalsIgnoreCase(CENTER))
418 alignment = CellConstraints.CENTER;
419 else if (value.equalsIgnoreCase(LEFT))
420 alignment = CellConstraints.LEFT;
421 else if (value.equalsIgnoreCase(RIGHT))
422 alignment = CellConstraints.RIGHT;
423 else if (value.equalsIgnoreCase(TOP))
424 alignment = CellConstraints.TOP;
425 else if (value.equalsIgnoreCase(BOTTOM))
426 alignment = CellConstraints.BOTTOM;
427 else
428 throw new RuntimeException("Invalid alignment");
429
430 return alignment;
431 }
432
433 /***
434 * Returns a LayoutConstraintsManager based on an input stream for an xml
435 * file. The root node in the xml file should be called <code>containers</code> and should
436 * adhere to the xml format for this tool.
437 */
438 public static LayoutConstraintsManager getLayoutConstraintsManager(
439 InputStream stream)
440 {
441 Document dataDocument = null;
442
443 try
444 {
445 DocumentBuilder documentBuilder = DocumentBuilderFactory.newInstance()
446 .newDocumentBuilder();
447 dataDocument = documentBuilder.parse(stream);
448 }
449 catch (Exception e)
450 {
451 e.printStackTrace();
452 throw new RuntimeException("Unable to create DocumentBuilder", e);
453 }
454
455 Node root = dataDocument.getDocumentElement();
456 return getLayoutConstraintsManager(root);
457 }
458
459 /***
460 * Returns a layout constraints manager given a containers node. This will
461 * enable you to keep a lot of different constraints in a single file or at
462 * least provide a little more flexibility.
463 */
464 public static LayoutConstraintsManager getLayoutConstraintsManager(
465 Node containersNode)
466 {
467
468 if (!containersNode.getNodeName().equals("containers"))
469 throw new RuntimeException("Expected a node named containers");
470
471 LayoutConstraintsManager layoutConstraintsManager = new LayoutConstraintsManager();
472 Node[] containerNodes = getNodesNamed(containersNode, "container");
473
474 for (int index = 0; index < containerNodes.length; index++)
475 {
476 Node containerNode = containerNodes[index];
477
478 Map<String, String> containerAttributes = getAttributeMap(containerNode);
479 String containerName = containerAttributes.get("name");
480 if (containerName == null)
481 throw new RuntimeException("Container must have a name attribute");
482 String columnSpecs = containerAttributes.get("columnSpecs") != null ? containerAttributes
483 .get("columnSpecs")
484 : "";
485 String rowSpecs = containerAttributes.get("rowSpecs") != null ? containerAttributes
486 .get("rowSpecs")
487 : "";
488
489 final ContainerLayout containerLayout = new ContainerLayout(
490 containerName, columnSpecs, rowSpecs);
491
492 Node[] cellConstraints = getNodesNamed(containerNode, "cellconstraints");
493 for (int cIndex = 0; cIndex < cellConstraints.length; cIndex++)
494 {
495 Map<String, String> constraintAttributes = getAttributeMap(cellConstraints[cIndex]);
496
497 String name = null;
498 CellConstraints.Alignment horizontalAlignment = CellConstraints.DEFAULT;
499 CellConstraints.Alignment verticalAlignment = CellConstraints.DEFAULT;
500 int gridX = 1;
501 int gridY = 1;
502 int gridWidth = 1;
503 int gridHeight = 1;
504 int topInset = 0;
505 int bottomInset = 0;
506 int rightInset = 0;
507 int leftInset = 0;
508
509 if (constraintAttributes.get("name") == null)
510 throw new RuntimeException(
511 "cellconstraints attribute name cannot be null for container "
512 + containerName);
513 name = constraintAttributes.get("name");
514 if (constraintAttributes.get("horizontalAlignment") != null)
515 horizontalAlignment = getAlignment(constraintAttributes
516 .get("horizontalAlignment"));
517 if (constraintAttributes.get("verticalAlignment") != null)
518 verticalAlignment = getAlignment(constraintAttributes
519 .get("verticalAlignment"));
520 if (constraintAttributes.get("gridX") != null)
521 gridX = Integer.parseInt(constraintAttributes.get("gridX"));
522 if (constraintAttributes.get("gridY") != null)
523 gridY = Integer.parseInt(constraintAttributes.get("gridY"));
524 if (constraintAttributes.get("gridWidth") != null)
525 gridWidth = Integer.parseInt(constraintAttributes.get("gridWidth"));
526 if (constraintAttributes.get("gridHeight") != null)
527 gridHeight = Integer.parseInt(constraintAttributes.get("gridHeight"));
528 if (constraintAttributes.get("topInset") != null)
529 topInset = Integer.parseInt(constraintAttributes.get("topInset"));
530 if (constraintAttributes.get("bottomInset") != null)
531 bottomInset = Integer.parseInt(constraintAttributes
532 .get("bottomInset"));
533 if (constraintAttributes.get("rightInset") != null)
534 rightInset = Integer.parseInt(constraintAttributes.get("rightInset"));
535 if (constraintAttributes.get("leftInset") != null)
536 leftInset = Integer.parseInt(constraintAttributes.get("leftInset"));
537
538 CellConstraints constraints = new CellConstraints(gridX, gridY,
539 gridWidth, gridHeight, horizontalAlignment, verticalAlignment,
540 new Insets(topInset, leftInset, bottomInset, rightInset));
541
542 containerLayout.addCellConstraints(name, constraints);
543 }
544
545 Node[] propertiesNodes = getNodesNamed(containerNode, "properties");
546
547
548
549
550 StringBuffer fakeDoc = new StringBuffer("<java version=\"1.4.0\" class=\"java.beans.XMLDecoder\">");
551 fakeDoc.append("<void id=\"controller\" property=\"owner\"/>\n");
552 fakeDoc.append("<object idref=\"controller\">");
553
554 for (int pIndex = 0; pIndex < propertiesNodes.length; pIndex++)
555 {
556 Node propertiesNode = propertiesNodes[pIndex];
557 Map<String, String> propertyAttributes = getAttributeMap(propertiesNode);
558 String componentName = propertyAttributes.get("component");
559 if (componentName == null)
560 throw new RuntimeException(
561 "propertyset must have an attribute called component");
562
563 Node[] propertyNodes = getNodesNamed(propertiesNode, "property");
564 for (int propIndex = 0; propIndex < propertyNodes.length; propIndex++)
565 {
566 Node propertyNode = propertyNodes[propIndex];
567 Map<String, String> voidAttributes = getAttributeMap(propertyNode);
568 String property = voidAttributes.get("name");
569 if (property == null)
570 throw new RuntimeException("property element must have a name");
571 fakeDoc.append("<void method=\"setProperty\"><string>" + componentName
572 + "</string>");
573 fakeDoc.append("<string>" + property + "</string>");
574 fakeDoc.append(createString(propertyNode.getChildNodes()));
575 fakeDoc.append("</void>\n");
576
577 }
578 }
579
580 fakeDoc.append("</object></java>");
581
582 if (propertiesNodes.length > 0)
583 {
584
585 Object controller = new Object()
586 {
587 public void configureProperty(String componentName, String property,
588 Object value)
589 {
590 containerLayout.setProperty(componentName, property, value);
591 }
592 };
593
594 XMLDecoder decoder = new XMLDecoder(new ByteArrayInputStream(fakeDoc
595 .toString().getBytes()));
596 decoder.setOwner(containerLayout);
597 decoder.readObject();
598 decoder.close();
599 }
600
601 layoutConstraintsManager.addLayout(containerLayout);
602 }
603
604 return layoutConstraintsManager;
605 }
606
607 private static Map<String, String> getAttributeMap(Node node)
608 {
609
610 Map<String, String> attributeMap = new HashMap<String, String>();
611
612 NamedNodeMap attributes = node.getAttributes();
613 if (attributes != null)
614 {
615 for (int index = 0; index < attributes.getLength(); index++)
616 {
617 Node attribute = attributes.item(index);
618 attributeMap.put(attribute.getNodeName(), attribute.getNodeValue());
619 }
620 }
621
622 return attributeMap;
623 }
624
625 private static Node[] getNodesNamed(Node parent, String nodeName)
626 {
627 NodeList children = parent.getChildNodes();
628 List<Node> childList = new ArrayList<Node>();
629 for (int i = 0; i < children.getLength(); i++)
630 {
631 if (nodeName.equals(children.item(i).getNodeName()))
632 childList.add(children.item(i));
633 }
634 Node[] result = new Node[childList.size()];
635 return (Node[]) childList.toArray(result);
636 }
637
638 public static void main(String[] args)
639 {
640 LayoutConstraintsManager l = LayoutConstraintsManager
641 .getLayoutConstraintsManager(LayoutConstraintsManager.class
642 .getResourceAsStream("editableLayoutConstraints.xml"));
643 ContainerLayout cl = l.getContainerLayout("mainLayout");
644 }
645
646 }