001: package net.xoetrope.swing;
002:
003: import java.util.Hashtable;
004: import java.util.Stack;
005: import java.util.Vector;
006:
007: import java.awt.Component;
008: import java.awt.Font;
009: import java.awt.FontMetrics;
010: import java.awt.Graphics;
011: import java.awt.ScrollPane;
012: import javax.swing.JComponent;
013:
014: import net.xoetrope.xml.XmlElement;
015: import net.xoetrope.xui.XMetaContentHolder;
016: import net.xoetrope.xui.style.XStyle;
017: import net.xoetrope.xui.style.XStyleManager;
018: import net.xoetrope.xui.style.XStyleComponent;
019: import net.xoetrope.xui.XResourceManager;
020: import net.xoetrope.xml.XmlSource;
021: import net.xoetrope.xui.XProjectManager;
022: import net.xoetrope.debug.DebugLogger;
023:
024: /**
025: * <p>This component renders XML based content. A simple set of tags
026: * for layout and formatting are used. Styles can be used to control attributes
027: * such as fonts and colors</p>
028: * <p>Copyright (c) Xoetrope Ltd., 1998-2004<br>
029: * License: see license.txt
030: * @version 1.0
031: */
032:
033: public class XMetaContent extends JComponent implements
034: XMetaContentHolder, XStyleComponent {
035: private Stack styleStack;
036: private XmlElement content;
037: private String strContent;
038: private String source;
039:
040: private int currentX, currentY;
041: private int width, height;
042:
043: private int startX, colStart;
044:
045: private XStyle currentStyle;
046: private String currentStyleName;
047: private Font font;
048: private FontMetrics fontMetrics;
049: private int fontHeight;
050: private int contentHeight;
051: private int padding;
052:
053: /**
054: * A table of the method names needed for rendereing each style
055: */
056: protected static Hashtable methodTable = null;
057:
058: private static final int XSTYLE = 1;
059: private static final int BR = 2;
060: private static final int UL = 3;
061: private static final int LI = 4;
062: private static final int TABLE = 5;
063: private static final int TR = 6;
064: private static final int TD = 7;
065:
066: /**
067: * Constructs anew XMetaContent component
068: */
069: public XMetaContent() {
070: styleStack = new Stack();
071: currentStyle = new XStyle();
072: currentStyleName = "";
073:
074: fillMethodTable();
075: }
076:
077: /**
078: * Initialises the lookup hash table of handlers for the content. The hash table
079: * provides a quick way of looking up the tag names for subsequent use in a
080: * switch.
081: */
082: protected void fillMethodTable() {
083: if (methodTable == null) {
084: methodTable = new Hashtable();
085: methodTable.put("xstyle", new Integer(XSTYLE));
086: methodTable.put("br", new Integer(BR));
087: methodTable.put("ul", new Integer(UL));
088: methodTable.put("li", new Integer(LI));
089: methodTable.put("table", new Integer(TABLE));
090: methodTable.put("tr", new Integer(TR));
091: methodTable.put("td", new Integer(TD));
092: }
093: }
094:
095: /**
096: * Set the meta content to a simple string value
097: * @param cont the new content
098: */
099: public void setContent(String newContent) {
100: strContent = newContent;
101: content = null;
102: source = null;
103: contentHeight = 0;
104: padding = 2;
105: calcSize();
106: repaint();
107: }
108:
109: /**
110: * Set the meta content to the content of an xml file
111: * @param fileName the new content
112: */
113: public void setFileName(String fileName) {
114: try {
115: if (fileName != null) {
116: XmlElement src = XmlSource.read(XResourceManager
117: .getBufferedReader(fileName, null));
118: setContent(fileName, src);
119: }
120: } catch (Exception ex) {
121: DebugLogger.logWarning("Unable to load content: "
122: + fileName);
123: DebugLogger
124: .logWarning("Please check the case of the file name");
125: ex.printStackTrace();
126: }
127: }
128:
129: /**
130: * Set the meta content.
131: * @param src the input xml
132: */
133: public void setContent(String xmlSrc, XmlElement src) {
134: source = xmlSrc;
135: content = src;
136: contentHeight = 0;
137: padding = 2;
138: calcSize();
139: repaint();
140: }
141:
142: /**
143: * Get the source of the current content
144: * @return
145: */
146: public String getFileName() {
147: return source;
148: }
149:
150: /**
151: * Get the meta content's name .
152: * @return the model name of the content
153: */
154: public String getContent() {
155: if (content != null)
156: return content.getName();
157: else
158: return strContent;
159: }
160:
161: /**
162: * Renders the component
163: * @param g the graphics context
164: */
165: public void paintComponent(Graphics g) {
166: currentX = currentY = startX = padding;
167: height = getSize().height - 2 * padding;
168:
169: g.setColor(getBackground());
170: g.fillRect(0, 0, getSize().width, getSize().height);
171: if ((content != null)
172: && (content.getName().compareTo("XText") == 0)) {
173: font = g.getFont();
174: if (font.getSize() == 0)
175: font = new Font(null, 0, 12);
176: g.setFont(font);
177:
178: if (contentHeight == 0)
179: calcSize();
180: else
181: render(g, content);
182: } else if ((content == null) && (strContent != null)) {
183: applyStyle(g);
184: renderText(g, strContent);
185: }
186:
187: int renderHeight = currentY + fontHeight + 2 * padding;
188: if (renderHeight != contentHeight) {
189: Component parent = getParent();
190: contentHeight = renderHeight;
191: if (parent instanceof ScrollPane) {
192: setSize(getSize().width - 40, contentHeight);
193: parent.doLayout();
194: } else
195: repaint();
196: }
197: }
198:
199: /**
200: * Sets the padding or indent for the content
201: */
202:
203: public void setPadding(int pad) {
204: padding = pad;
205: }
206:
207: /**
208: * Calcualte the size of the content. This method is called from within the
209: * paint method and recalculates the required size for display of the content.
210: * If a scrollpane is the parent then this control is resized so that all the
211: * content will be visible. The scrollpane may initially have no scrollbar so
212: * to avoid flicker and multiple repaints as the control is sized and offscreen
213: * graphics context is used for the sizing.
214: */
215: public void calcSize() {
216: width = getSize().width;
217: if (contentHeight == 0)
218: width -= 20;
219: contentHeight = -1;
220: java.awt.Image offscreen = createImage(1, 1);
221:
222: if (offscreen != null) {
223: Graphics og = offscreen.getGraphics();
224: og.setClip(0, 0, width, height);
225: paint(og);
226: og.dispose();
227: }
228: }
229:
230: /**
231: * Renders the content, recursively descending the XML
232: * @param g the graphics context
233: * @param element the root element
234: */
235: private void render(Graphics g, XmlElement element) {
236: applyStyle(g);
237:
238: Vector elements = element.getChildren();
239: int numElements = elements.size();
240:
241: for (int i = 0; i < numElements; i++) {
242: XmlElement child = (XmlElement) elements.elementAt(i);
243:
244: int method = 0;
245: method = ((Integer) methodTable.get(child.getName()
246: .toLowerCase())).intValue();
247:
248: if (!renderItem(g, child, method))
249: renderText(g, child.getContent());
250:
251: pushStyle();
252: render(g, child);
253: popStyle();
254: }
255: }
256:
257: /**
258: * Renders an individual item/tag
259: * @param g the graphics context
260: * @param child the xml element being rendered
261: * @param method the element name
262: * @return
263: */
264: protected boolean renderItem(Graphics g, XmlElement child,
265: int method) {
266: switch (method) {
267: case XSTYLE:
268: modifyStyle(g, child.getContent(), (String) child
269: .getAttribute("name"));
270: break;
271: case BR:
272: lineBreak(g);
273: break;
274: case UL:
275: renderList(g);
276: break;
277: case LI:
278: renderListItem(g, child.getContent());
279: break;
280: case TABLE:
281: renderTable(g);
282: break;
283: case TR:
284: renderTableRecord(g);
285: break;
286: case TD:
287: renderTableCell(g, child.getContent(), child.getAttribute(
288: "width").toString());
289: break;
290: default:
291: return false;
292: }
293:
294: return true;
295: }
296:
297: /**
298: * Set the current style.
299: * @param style
300: */
301: public void setStyle(String style) {
302: currentStyle = XProjectManager.getStyleManager()
303: .getStyle(style);
304: }
305:
306: /**
307: * Changes the current style. Teh style element may have content and it is
308: * assumed to be text when rendering.
309: * @param g teh graphics context
310: * @param content the current xml node
311: * @param styleName the style name
312: */
313: private void modifyStyle(Graphics g, String content,
314: String styleName) {
315: if (styleName.compareTo(currentStyleName) != 0) {
316: XStyle newStyle = XProjectManager.getStyleManager()
317: .getStyle("base/" + styleName);
318: currentStyle.mergeStyle(newStyle);
319: currentStyleName = styleName;
320:
321: applyStyle(g);
322: }
323:
324: renderText(g, content);
325: }
326:
327: /**
328: * Applies the current style to the graphics context
329: * @param g teh graphics context
330: */
331: private void applyStyle(Graphics g) {
332: g.setColor(currentStyle.getStyleAsColor(XStyle.COLOR_FORE));
333:
334: try {
335: font = XProjectManager.getStyleManager().getFont(
336: currentStyle);
337: } catch (Exception ex) {
338: font = new Font("Arial", 0, 12);
339: }
340: if (font != null)
341: g.setFont(font);
342: fontMetrics = g.getFontMetrics();
343: fontHeight = fontMetrics.getHeight();
344: }
345:
346: /**
347: * Pushes the style onto the style stack. Called prior to descending a level
348: * in the xml
349: */
350: private void pushStyle() {
351: styleStack.push(currentStyle.clone());
352: }
353:
354: /**
355: * Pops the style from the style stack. Called on return from a recursive call
356: * to render a lower level of the xml hierarchy
357: */
358: private void popStyle() {
359: currentStyle = (XStyle) styleStack.pop();
360: }
361:
362: /**
363: * Renders text content
364: * @param g the graphcis context
365: * @param text the text content
366: */
367: private void renderText(Graphics g, String text) {
368: // Draw the question text over multiple lines
369: g.setColor(currentStyle.getStyleAsColor(XStyle.COLOR_BACK));
370: int start = 0;
371: int end = 0;
372: String line;
373: int lineNum = 1;
374:
375: boolean newLine = false;
376: do {
377: boolean drawLine = false;
378:
379: // Extend the string by a word (to the next space, if any)
380: end = text.indexOf(' ', end + 1);
381: String ss;
382: if (end > 0)
383: ss = text.substring(start, end);
384: else {
385: ss = text.substring(start);
386: drawLine = true;
387: }
388:
389: // Does the next word fit in?
390: if ((currentX + fontMetrics.stringWidth(ss)) < width)
391: line = ss;
392: else {
393: end = ss.lastIndexOf(' ');
394: if (end >= 0) {
395: end += start;
396: line = ss = text.substring(start, end);
397: } else
398: line = ss = text.substring(start);
399: drawLine = true;
400: newLine = true;
401: }
402:
403: if (drawLine) {
404: // Erase the background
405: g.setColor(getBackground());
406: g.fillRect(currentX, currentY, (int) fontMetrics
407: .stringWidth(ss) + 1, fontHeight + 1);
408:
409: // Draw the text
410: g.setColor(getForeground());
411: g.drawString(ss, currentX, currentY
412: + fontMetrics.getAscent());
413:
414: // Update the current position.
415: currentX += (int) fontMetrics.stringWidth(ss);
416: start = end + 1;
417: lineNum++;
418: if (newLine) {
419: currentY += fontHeight;
420: currentX = startX;
421: newLine = false;
422: }
423: }
424: } while (end >= 0);
425:
426: // If no text drawn, then do it now
427: if ((start == 0) && (lineNum == 1))
428: g.drawString(text, currentX, fontMetrics.getAscent());
429: }
430:
431: /**
432: * Render a list
433: * @param g
434: */
435: private void renderList(Graphics g) {
436: lineBreak(g);
437: }
438:
439: /**
440: * Render a list item
441: * @param g
442: * @param text
443: */
444: private void renderListItem(Graphics g, String text) {
445: int oldStart = startX;
446: int bulletSize = fontHeight / 4;
447:
448: currentY += fontHeight / 2;
449:
450: startX += 2 * fontHeight;
451: lineBreak(g);
452:
453: g.fillOval(startX - fontHeight / 2, currentY + fontHeight / 2,
454: bulletSize, bulletSize);
455:
456: renderText(g, text);
457: startX = oldStart;
458: }
459:
460: /**
461: * Prepare to render a table element
462: * @param g
463: */
464: private void renderTable(Graphics g) {
465: startX += 2 * fontHeight;
466: colStart = startX;
467: lineBreak(g);
468: }
469:
470: /**
471: * Render a record of a table.
472: * @param g
473: */
474: private void renderTableRecord(Graphics g) {
475: lineBreak(g);
476: startX = colStart;
477: currentX = startX;
478: }
479:
480: /**
481: * Render an individual table cell
482: * @param g
483: * @param text
484: * @param width
485: */
486: private void renderTableCell(Graphics g, String text, String width) {
487: int bulletSize = fontHeight / 4;
488:
489: renderText(g, text);
490:
491: startX += Integer.parseInt(width);
492: currentX = startX;
493: }
494:
495: /**
496: * Add a line break
497: * @param g
498: */
499: private void lineBreak(Graphics g) {
500: currentY += fontHeight;
501: currentX = startX;
502: }
503:
504: /**
505: * Sets the bounds for this component
506: * @param x
507: * @param y
508: * @param w
509: * @param h
510: */
511: public void setBounds(int x, int y, int w, int h) {
512: super.setBounds(x, y, w, h);
513: doLayout();
514: if ((w != width) || (height != h))
515: calcSize();
516: }
517: }
|