001: package org.osbl.client.wings.shell;
002:
003: import java.awt.event.ActionEvent;
004: import javax.swing.JFileChooser;
005: import javax.swing.JProgressBar;
006: import org.apache.batik.bridge.*;
007: import org.apache.batik.dom.svg.SAXSVGDocumentFactory;
008: import org.apache.batik.dom.svg.SVGDocumentFactory;
009: import org.apache.batik.gvt.GraphicsNode;
010: import org.apache.batik.gvt.renderer.StaticRenderer;
011: import org.apache.batik.util.XMLResourceDescriptor;
012: import org.apache.commons.logging.Log;
013: import org.apache.commons.logging.LogFactory;
014: import org.w3c.dom.Document;
015: import org.w3c.dom.Element;
016: import org.w3c.dom.svg.SVGDocument;
017: import org.wings.*;
018: import org.wings.session.BrowserType;
019: import org.wings.session.SessionManager;
020: import org.wings.util.SessionLocal;
021:
022: import java.awt.*;
023: import java.awt.image.BufferedImage;
024: import java.io.BufferedReader;
025: import java.io.ByteArrayInputStream;
026: import java.io.File;
027: import java.io.IOException;
028: import java.io.InputStream;
029: import java.io.InputStreamReader;
030: import java.util.*;
031: import javax.swing.AbstractAction;
032: import javax.swing.ImageIcon;
033: import javax.swing.JButton;
034: import javax.swing.JFrame;
035: import javax.swing.JLabel;
036: import javax.swing.JPanel;
037: import javax.swing.SwingUtilities;
038: import javax.swing.filechooser.FileFilter;
039:
040: public class SVGButtonRenderer {
041: private static final transient Log log = LogFactory
042: .getLog(SVGButtonRenderer.class);
043:
044: SessionLocal iconCaches = new SessionLocal() {
045: protected Object initialValue() {
046: return Collections
047: .synchronizedMap(new HashMap<BufferedImage, SIcon>());
048: }
049: };
050: protected static final Map<String, BufferedImage> renderedImageCache = Collections
051: .synchronizedMap(new HashMap<String, BufferedImage>());
052:
053: /**
054: * The returned width of the textwidths somehow does not fit the real life. This correcture factor can be used to adjust.
055: */
056: protected static final double TEXTWIDTH_CORRECTURE_FACTOR = 1.0;
057:
058: private static SVGButtonRenderer instance = null;
059:
060: private SVGButtonRenderer() {
061: }
062:
063: /**
064: * This method provides dynamical rendered buttons ....
065: *
066: * @param buttonText The text which should appear inside the button
067: * @param buttonTemplateUri The template file for the button
068: */
069: public SIcon retrieveRenderedButtonIcon(String buttonText,
070: String buttonTemplateUri, Dimension imageSize) {
071: BufferedImage bufferedImage = retrieveRenderedButtonImage(
072: buttonText, buttonTemplateUri, imageSize);
073: Map<BufferedImage, SIcon> renderedIconCache = (Map<BufferedImage, SIcon>) iconCaches
074: .get();
075: SIcon icon = renderedIconCache.get(bufferedImage);
076: if (icon == null) {
077: icon = new SImageIcon(bufferedImage, isIE() ? "image/gif"
078: : "image/png");
079: renderedIconCache.put(bufferedImage, icon);
080: }
081: return icon;
082: }
083:
084: private boolean isIE() {
085: return BrowserType.IE.equals(SessionManager.getSession()
086: .getUserAgent().getBrowserType());
087: }
088:
089: /**
090: * This method provides dynamical rendered buttons ....
091: *
092: * @param buttonText The text which should appear inside the button
093: * @param buttonTemplateUri The template file for the button
094: */
095: public BufferedImage retrieveRenderedButtonImage(String buttonText,
096: String buttonTemplateUri, Dimension imageSize) {
097: if (buttonText == null) {
098: buttonText = "";
099: }
100:
101: // Cached version available?!
102: String cacheKey = buttonTemplateUri + buttonText;
103: BufferedImage cachedVersion = renderedImageCache.get(cacheKey);
104: if (cachedVersion != null
105: && imageSize.width == cachedVersion.getWidth()
106: && imageSize.height == cachedVersion.getHeight()) {
107: log
108: .debug("Returning CACHED version of ButtonImage for button '"
109: + buttonText
110: + "' with template '"
111: + buttonTemplateUri + "'");
112: return cachedVersion;
113: }
114:
115: if (imageSize == null) {
116: imageSize = calculateButtonImageSize(buttonText);
117: }
118:
119: return renderAndCacheNewButton(buttonText, buttonTemplateUri,
120: imageSize);
121: }
122:
123: private synchronized BufferedImage renderAndCacheNewButton(
124: String buttonText, String buttonTemplateURI,
125: Dimension imageSize) {
126: if (buttonText == null) {
127: buttonText = "";
128: }
129: log.debug("Rendering unknown image button for button '"
130: + buttonText + "'");
131: String cacheLookupKey = buttonTemplateURI.concat(buttonText);
132:
133: // Recheck if cached version is available, perhaps other thread already generated this button?
134: BufferedImage cachedVersion = renderedImageCache
135: .get(cacheLookupKey);
136: if (cachedVersion != null
137: && imageSize.width == cachedVersion.getWidth()
138: && imageSize.height == cachedVersion.getHeight()) {
139: log
140: .warn("Concurrent thread seems to have already rendered button'"
141: + buttonText
142: + "'. Returning existing instance.");
143: return (BufferedImage) renderedImageCache
144: .get(cacheLookupKey);
145: }
146:
147: // Render button
148: long renderingTime = System.currentTimeMillis();
149: BufferedImage img = renderButton(buttonText, buttonTemplateURI,
150: imageSize);
151: renderingTime = System.currentTimeMillis() - renderingTime;
152: log.debug("Finished rendering button in " + renderingTime
153: + " ms. Caching and returning result.");
154: renderedImageCache.put(cacheLookupKey, img);
155: return img;
156: }
157:
158: /**
159: * This method takes the given button text, inserts it into a SVG template document retrieved via the classpath, renders
160: * the resulting SVG image into an image and returns this Image
161: *
162: * @param buttonText The text which should appear inside the button.
163: */
164: private BufferedImage renderButton(String buttonText,
165: String buttonTemplateURI, Dimension buttonImageSize) {
166: GVTBuilder builder = new GVTBuilder();
167: UserAgent userAgent = new Log4jUserAgentAdapter();
168: DocumentLoader loader = new DocumentLoader(userAgent);
169: BridgeContext ctx = new BridgeContext(userAgent, loader);
170: StaticRenderer renderer = new StaticRenderer();
171: GraphicsNode gvtRoot = null;
172: BufferedImage image = null;
173:
174: try {
175: // create according svg document
176: Document svgDoc = createButtonDocument(buttonText,
177: buttonImageSize, buttonTemplateURI);
178: log.debug("Building GVT Tree");
179: gvtRoot = builder.build(ctx, svgDoc);
180: log.debug("Populating GVT tree rood node to renderer ["
181: + gvtRoot + "]");
182: renderer.setTree(gvtRoot);
183:
184: // Allocate according image
185: BufferedImage display = new BufferedImage(
186: (int) buttonImageSize.getWidth(),
187: (int) buttonImageSize.getHeight(),
188: BufferedImage.TYPE_INT_BGR);
189:
190: // Set Transform (?!)
191: Element elt = ((SVGDocument) svgDoc).getRootElement();
192: renderer.setTransform(ViewBox.getViewTransform(null, elt,
193: display.getWidth(), display.getHeight()));
194:
195: // Cacluculate offscreen Image
196: renderer.updateOffScreen(display.getWidth(), display
197: .getHeight());
198:
199: //Repaint whole image
200: Rectangle r = new Rectangle(0, 0, display.getWidth(),
201: display.getHeight());
202: renderer.repaint(r);
203:
204: // Return resulting image
205: image = renderer.getOffScreen();
206: } catch (Exception e) {
207: log.fatal("Uncaught Exception", e);
208: }
209: return image;
210: }
211:
212: public static SVGButtonRenderer getInstance() {
213: if (instance == null) {
214: synchronized (SVGButtonRenderer.class) {
215: if (instance == null)
216: instance = new SVGButtonRenderer();
217: }
218: }
219: return instance;
220: }
221:
222: /**
223: * Calls the method which creates a String with the SVG XML Document and parses it into an XML SVGDocument.
224: */
225: private SVGDocument createButtonDocument(String buttonText,
226: Dimension buttonImageSize, String buttonTemplateURI) {
227: log.debug("Creating SVG template document");
228: try {
229: String templateDocument = prepareTemplateDocument(
230: buttonText, buttonImageSize, buttonTemplateURI);
231:
232: log
233: .debug("Parsing resulting Document String to an SVGDocument");
234: ByteArrayInputStream istream = new ByteArrayInputStream(
235: templateDocument.getBytes("UTF-8"));
236: SVGDocumentFactory svgDocumentFactory = new SAXSVGDocumentFactory(
237: XMLResourceDescriptor.getXMLParserClassName());
238: return svgDocumentFactory.createSVGDocument(null, istream);
239: } catch (Exception e) {
240: log.fatal("Uncaught Exception", e);
241: }
242: return null;
243: }
244:
245: /**
246: * Calculates the estimated size for the button immage.
247: */
248: public Dimension calculateButtonImageSize(String buttonText) {
249: if (buttonText == null) {
250: buttonText = "";
251: }
252: log.debug("Estimating image size for button with label '"
253: + buttonText + "'");
254: Font font = new Font("sans-serif", Font.BOLD, 10);
255: // XXX - Hack
256: // This was previously new Label(), and this threw a HeadLessException.
257: // Heavyweight components DON'T concern in headless environments
258: // It's still pretty stupid to use JLabel, but we need a quick fix
259: FontMetrics fm = new JLabel().getFontMetrics(font);
260:
261: Dimension maxSize = new Dimension(Integer.MAX_VALUE,
262: Integer.MAX_VALUE);
263: Dimension minSize = new Dimension(0, 0);
264:
265: //Calculate the approximate space for the text
266: int textWidth = fm.stringWidth(buttonText);
267: int height = 20;
268: int horizontalBorder = 21; // BSC: orig "20" but due to wrong font width with short texts (i.e. Ok) expanded.
269: int width = ((int) (textWidth * TEXTWIDTH_CORRECTURE_FACTOR))
270: + horizontalBorder; // Apply lenght calculation correction factor.
271:
272: // Adjust size to min/max requirements
273: if (width > maxSize.width)
274: width = maxSize.width;
275: else if (width < minSize.width)
276: width = minSize.width;
277: if (height > maxSize.height)
278: height = maxSize.height;
279: else if (height < minSize.height)
280: height = minSize.height;
281:
282: return new Dimension(width, height);
283: }
284:
285: /**
286: * The platform-specific implementation of how to retrieve the actual button template document. This implementation
287: * differs for servlet/wings/swing applications.
288: */
289: private InputStream getTemplateDocument(String templateFileURI) {
290: InputStream templateDocument = Thread.currentThread()
291: .getContextClassLoader().getResourceAsStream(
292: templateFileURI);
293: if (templateDocument == null)
294: log.warn("Failed to retrieve template document +'"
295: + templateFileURI + "'. Check URI/implementation!");
296: return templateDocument;
297: }
298:
299: /**
300: * Reads the template document from the given URI and resolves the included tags.
301: */
302: private String prepareTemplateDocument(String templateText,
303: Dimension buttonSize, String templateFileURI) {
304: InputStream templateFileStream = null;
305: InputStreamReader streamReader = null;
306: BufferedReader r = null;
307: try {
308: templateFileStream = getTemplateDocument(templateFileURI);
309: streamReader = new InputStreamReader(templateFileStream,
310: "ISO-8859-1");
311: r = new BufferedReader(streamReader);
312: StringBuffer templateDocumentString = new StringBuffer();
313: String line = "";
314:
315: log.debug("Reading template file " + templateFileURI);
316: while (line != null) {
317: templateDocumentString.append(line);
318: line = r.readLine();
319: }
320:
321: //templateDocumentString.append("\n");
322: log.debug("Replacing tokens.");
323: String template = replaceTokens(templateDocumentString
324: .toString(), templateText, (int) buttonSize
325: .getWidth(), (int) buttonSize.getHeight());
326:
327: log.debug("Returning resulting document " + template);
328: return template;
329: } catch (Exception ex) {
330: log.fatal("Uncatched Exception", ex);
331: } finally {
332: try {
333: if (r != null)
334: r.close();
335: if (streamReader != null)
336: streamReader.close();
337: if (templateFileStream != null)
338: templateFileStream.close();
339: } catch (IOException e) {
340: log.error("Unexpected error", e);
341: }
342: }
343: return null;
344: }
345:
346: /**
347: * Recursive method to replace all occurrences of template snippets like "@width@" ...
348: */
349: private String replaceTokens(String template, String buttonText,
350: int width, int height) {
351: // replace the text element
352: if (template.indexOf("@text@") >= 0)
353: template = template
354: .substring(0, template.indexOf("@text@"))
355: + buttonText
356: + template
357: .substring(template.indexOf("@text@") + 6);
358:
359: // find parse and replace the width element
360: if (template.indexOf("@width") >= 0) {
361: template = replaceSizeToken(template, "@width", width);
362: return replaceTokens(template, buttonText, width, height);
363: } else if (template.indexOf("@height") >= 0) {
364: template = replaceSizeToken(template, "@height", height);
365: return replaceTokens(template, buttonText, width, height);
366: } else
367: return template;
368: }
369:
370: /**
371: * Special methode to replace the token with pixel information.
372: * Such a token can look like this: @width-5@ This means we have to insert the width of the button minus 5 pixels.
373: * The optional number within the token can be "+" or "-"
374: */
375: private String replaceSizeToken(String template, String tokenName,
376: int value) {
377: int index = template.indexOf(tokenName);
378: if (index < 0)
379: return template;
380:
381: String element = template.substring(index, template.indexOf(
382: "@", index + 1));
383:
384: // parse if value has to be adjusted.. (example: @width-4@)
385: int diff = 0;
386: if (element.indexOf("-") >= 0)
387: diff = Integer.parseInt(element.substring(tokenName
388: .length()));
389: else if (element.indexOf("+") >= 0)
390: diff = Integer.parseInt(element.substring(tokenName
391: .length() + 1));
392:
393: //replace the element:
394: template = template.substring(0, index) + (value + diff)
395: + template.substring(index + element.length() + 1);
396:
397: return template;
398: }
399:
400: /**
401: * Pipes all apache-bastik message output for the user agent to log4j
402: */
403: static class Log4jUserAgentAdapter extends UserAgentAdapter {
404: public void displayMessage(String message) {
405: log.info(message);
406: }
407: }
408:
409: public static void main(String[] args) {
410: SwingUtilities.invokeLater(new Runnable() {
411: public void run() {
412: startApp();
413: }
414: });
415: }
416:
417: public static void startApp() {
418: final JFrame frWindow = new JFrame();
419: frWindow.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
420: final JPanel paMain = new JPanel();
421: final JProgressBar progressBar = new JProgressBar();
422: final SVGButtonRenderer brImageCreator = new SVGButtonRenderer();
423: progressBar.setVisible(false);
424: paMain.setLayout(new BorderLayout());
425:
426: JButton btLoadFile = new JButton(new AbstractAction(
427: "Load SVG file") {
428: public void actionPerformed(ActionEvent e) {
429: JFileChooser fcOpenSVGFile = new JFileChooser(
430: "/home/hengels/jdevel/osb3/osbuild/openshop/modules/core/src/web/WEB-INF/classes/svg/");
431: fcOpenSVGFile.setDialogTitle("Load SVG File");
432: fcOpenSVGFile.setMultiSelectionEnabled(false);
433: fcOpenSVGFile.addChoosableFileFilter(new FileFilter() {
434: public boolean accept(File pathname) {
435: return pathname.isFile()
436: && (pathname.getAbsolutePath()
437: .endsWith(".svg") || pathname
438: .getAbsolutePath().endsWith(
439: ".SVG"));
440: }
441:
442: public String getDescription() {
443: return "SVG Files";
444: }
445: });
446: if (fcOpenSVGFile.showOpenDialog(frWindow) == JFileChooser.APPROVE_OPTION) {
447: final File fSelectedFile = fcOpenSVGFile
448: .getSelectedFile();
449: //String filePath = selectedFile.getAbsolutePath();
450: //String normalizedPath = filePath.replace('\\', '/');
451: //String fileURI = "file://" + normalizedPath;
452: progressBar.setVisible(true);
453: progressBar.setIndeterminate(true);
454: //thread
455: Thread t = new Thread(new Runnable() {
456: public void run() {
457: //Dimension dim = brImageCreator.calculateButtonImageSize("Aufgaben");
458: Dimension dim = new Dimension(20, 100);
459: final BufferedImage biImageButton = brImageCreator
460: .retrieveRenderedButtonImage(
461: "Aufgaben", fSelectedFile
462: .getName(), dim);
463: SwingUtilities.invokeLater(new Runnable() {
464: public void run() {
465: Component c = ((BorderLayout) paMain
466: .getLayout())
467: .getLayoutComponent(BorderLayout.CENTER);
468: if (c != null) {
469: paMain.remove(c);
470: }
471: paMain.add(
472: new JButton(new ImageIcon(
473: biImageButton)),
474: BorderLayout.CENTER);
475: //paMain.setBackground(Color.YELLOW);
476: paMain.validate();
477:
478: //thread end
479: progressBar.setVisible(false);
480: }
481: });
482: }
483: });
484: t.start();
485: }
486: }
487: });
488: JButton btClean = new JButton(new AbstractAction("Clean") {
489: public void actionPerformed(ActionEvent e) {
490: Component c = ((BorderLayout) paMain.getLayout())
491: .getLayoutComponent(BorderLayout.CENTER);
492: if (c != null) {
493: paMain.remove(c);
494: }
495: paMain.validate();
496: }
497: });
498: JPanel paButtons = new JPanel();
499: paButtons.setLayout(new FlowLayout());
500: paButtons.add(btLoadFile);
501: paButtons.add(btClean);
502: paMain.add(paButtons, BorderLayout.NORTH);
503: paMain.add(progressBar, BorderLayout.SOUTH);
504: frWindow.setContentPane(paMain);
505: frWindow.setSize(new Dimension(640, 480));
506: //window.pack();
507: frWindow.setVisible(true);
508: }
509: }
|