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