001: /*
002: * Beryl - A web platform based on XML, XSLT and Java
003: * This file is part of the Beryl XML GUI
004: *
005: * Copyright (C) 2004 Wenzel Jakob <wazlaf@tigris.org>
006: *
007: * This program is free software; you can redistribute it and/or
008: * modify it under the terms of the GNU Lesser General Public
009: * License as published by the Free Software Foundation; either
010: * version 2.1 of the License, or (at your option) any later version.
011:
012: * This program is distributed in the hope that it will be useful,
013: * but WITHOUT ANY WARRANTY; without even the implied warranty of
014: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
015: * Lesser General Public License for more details.
016: *
017: * You should have received a copy of the GNU Lesser General Public
018: * License along with this program; if not, write to the Free Software
019: * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-3107 USA
020: */
021:
022: package org.beryl.gui.widgets;
023:
024: import java.awt.Color;
025: import java.awt.Component;
026: import java.awt.Font;
027: import java.io.IOException;
028: import java.util.Properties;
029: import java.util.StringTokenizer;
030:
031: import javax.swing.JScrollPane;
032: import javax.swing.event.DocumentEvent;
033: import javax.swing.event.DocumentListener;
034:
035: import novaworx.syntax.SyntaxFactory;
036: import novaworx.syntax.Token;
037: import novaworx.textpane.SyntaxDocument;
038: import novaworx.textpane.SyntaxGutter;
039: import novaworx.textpane.SyntaxGutterBase;
040: import novaworx.textpane.SyntaxStyle;
041: import novaworx.textpane.SyntaxTextPane;
042:
043: import org.beryl.gui.GUIException;
044: import org.beryl.gui.Widget;
045: import org.beryl.gui.WidgetInfo;
046: import org.beryl.gui.model.MapChangeEvent;
047: import org.beryl.gui.model.MapDataModel;
048: import org.beryl.gui.model.ModelChangeEvent;
049:
050: public class SyntaxEditor extends Widget {
051: private static final Color CLEAR = new Color(0, 0, 0, 0);
052: private static Properties themes = null;
053: protected static WidgetInfo syntaxInfo = null;
054: private SyntaxDocument document = null;
055: private SyntaxTextPane textPane = null;
056: private SyntaxGutter gutter = null;
057: private SyntaxGutterBase gutterBase = null;
058: private JScrollPane scrollPane = null;
059: private String key = null;
060: private boolean sendEvents = true;
061: private boolean processEvents = true;
062:
063: static {
064: syntaxInfo = new WidgetInfo(SyntaxEditor.class, widgetInfo);
065: syntaxInfo.addProperty("key", "string", "");
066: syntaxInfo.addProperty("verticalScrollBar", "bool",
067: Boolean.FALSE);
068: syntaxInfo.addProperty("horizontalScrollBar", "bool",
069: Boolean.FALSE);
070: syntaxInfo.addProperty("gutter", "bool", "true");
071: syntaxInfo.addProperty("lineHighlight", "bool", "true");
072: syntaxInfo.addProperty("bracketHighlight", "bool", "true");
073: syntaxInfo.addProperty("antiAlias", "bool", "true");
074: syntaxInfo.addProperty("theme", "string", "default");
075: syntaxInfo.addProperty("mode", "string", "xml");
076:
077: SyntaxFactory.setSyntaxCatalog(SyntaxEditor.class
078: .getResource("/resources/syntax.catalog.xml"));
079: SyntaxFactory.loadSyntaxes();
080:
081: try {
082: themes = new Properties();
083: themes
084: .load(SyntaxEditor.class.getResource(
085: "/resources/xmlgui/syntax.properties")
086: .openStream());
087: } catch (IOException e) {
088: throw new RuntimeException(e);
089: }
090: }
091:
092: public SyntaxEditor(Widget parent, String name) throws GUIException {
093: super (parent, name);
094:
095: document = new SyntaxDocument();
096: document.setSyntax(SyntaxFactory.getSyntax("xml"));
097:
098: textPane = new SyntaxTextPane();
099: textPane.setDocument(document);
100: textPane.setLineHighlight(true);
101: textPane.setBracketHighlight(true);
102: textPane.setAntiAlias(true);
103: textPane.setOpaque(true);
104:
105: scrollPane = new JScrollPane(textPane);
106:
107: gutter = new SyntaxGutter(textPane);
108: gutterBase = new SyntaxGutterBase(gutter);
109: scrollPane.setRowHeaderView(gutter);
110: scrollPane.setCorner(JScrollPane.LOWER_LEFT_CORNER, gutterBase);
111:
112: setTheme(themes, "default");
113: }
114:
115: private Color parseColor(String color) throws GUIException {
116: if (color == null)
117: return CLEAR;
118: try {
119: if (color.length() == 6) {
120: int r = Integer.parseInt(color.substring(0, 2), 16);
121: int g = Integer.parseInt(color.substring(2, 4), 16);
122: int b = Integer.parseInt(color.substring(4, 6), 16);
123: return new Color(r, g, b);
124: } else if (color.length() == 8) {
125: int a = Integer.parseInt(color.substring(0, 2), 16);
126: int r = Integer.parseInt(color.substring(2, 4), 16);
127: int g = Integer.parseInt(color.substring(4, 6), 16);
128: int b = Integer.parseInt(color.substring(6, 8), 16);
129: return new Color(r, g, b, a);
130: } else {
131: throw new GUIException("Invalid color syntax");
132: }
133: } catch (NumberFormatException e) {
134: throw new GUIException("Invalid color data", e);
135: }
136: }
137:
138: private int parseFont(String font) throws GUIException {
139: int flags = Font.PLAIN;
140: if (font != null) {
141:
142: StringTokenizer tokenizer = new StringTokenizer(font, ",");
143: while (tokenizer.hasMoreTokens()) {
144: String token = tokenizer.nextToken().toLowerCase();
145:
146: if (token.equals("bold"))
147: flags |= Font.BOLD;
148: else if (token.equals("italic"))
149: flags |= Font.ITALIC;
150: else if (token.equals("plain"))
151: ;
152: else
153: throw new GUIException("Unknown font attribute");
154: }
155: }
156: return flags;
157: }
158:
159: private void setTheme(Properties props, String prefix)
160: throws GUIException {
161: SyntaxStyle[] aStyles = textPane.getSyntaxStyles();
162:
163: aStyles[Token.COMMENT1].setForeground(parseColor(props
164: .getProperty(prefix + ".comment1.fg")));
165: aStyles[Token.COMMENT1].setBackground(parseColor(props
166: .getProperty(prefix + ".comment1.bg")));
167: aStyles[Token.COMMENT1].setFontStyle(parseFont(props
168: .getProperty(prefix + ".comment1.font")));
169: aStyles[Token.COMMENT2].setForeground(parseColor(props
170: .getProperty(prefix + ".comment2.fg")));
171: aStyles[Token.COMMENT2].setBackground(parseColor(props
172: .getProperty(prefix + ".comment2.bg")));
173: aStyles[Token.COMMENT2].setFontStyle(parseFont(props
174: .getProperty(prefix + ".comment2.font")));
175: aStyles[Token.COMMENT3].setForeground(parseColor(props
176: .getProperty(prefix + ".comment3.fg")));
177: aStyles[Token.COMMENT3].setBackground(parseColor(props
178: .getProperty(prefix + ".comment3.bg")));
179: aStyles[Token.COMMENT3].setFontStyle(parseFont(props
180: .getProperty(prefix + ".comment3.font")));
181: aStyles[Token.DIGIT].setForeground(parseColor(props
182: .getProperty(prefix + ".digit.fg")));
183: aStyles[Token.DIGIT].setBackground(parseColor(props
184: .getProperty(prefix + ".digit.bg")));
185: aStyles[Token.DIGIT].setFontStyle(parseFont(props
186: .getProperty(prefix + ".digit.font")));
187: aStyles[Token.LITERAL1].setForeground(parseColor(props
188: .getProperty(prefix + ".literal1.fg")));
189: aStyles[Token.LITERAL1].setBackground(parseColor(props
190: .getProperty(prefix + ".literal1.bg")));
191: aStyles[Token.LITERAL1].setFontStyle(parseFont(props
192: .getProperty(prefix + ".literal1.font")));
193: aStyles[Token.LITERAL2].setForeground(parseColor(props
194: .getProperty(prefix + ".literal2.fg")));
195: aStyles[Token.LITERAL2].setBackground(parseColor(props
196: .getProperty(prefix + ".literal2.bg")));
197: aStyles[Token.LITERAL2].setFontStyle(parseFont(props
198: .getProperty(prefix + ".literal2.font")));
199: aStyles[Token.KEYWORD1].setForeground(parseColor(props
200: .getProperty(prefix + ".keyword1.fg")));
201: aStyles[Token.KEYWORD1].setBackground(parseColor(props
202: .getProperty(prefix + ".keyword1.bg")));
203: aStyles[Token.KEYWORD1].setFontStyle(parseFont(props
204: .getProperty(prefix + ".keyword1.font")));
205: aStyles[Token.KEYWORD2].setForeground(parseColor(props
206: .getProperty(prefix + ".keyword2.fg")));
207: aStyles[Token.KEYWORD2].setBackground(parseColor(props
208: .getProperty(prefix + ".keyword2.bg")));
209: aStyles[Token.KEYWORD2].setFontStyle(parseFont(props
210: .getProperty(prefix + ".keyword2.font")));
211: aStyles[Token.KEYWORD3].setForeground(parseColor(props
212: .getProperty(prefix + ".keyword3.fg")));
213: aStyles[Token.KEYWORD3].setBackground(parseColor(props
214: .getProperty(prefix + ".keyword3.bg")));
215: aStyles[Token.KEYWORD3].setFontStyle(parseFont(props
216: .getProperty(prefix + ".keyword3.font")));
217: aStyles[Token.KEYWORD4].setForeground(parseColor(props
218: .getProperty(prefix + ".keyword4.fg")));
219: aStyles[Token.KEYWORD4].setBackground(parseColor(props
220: .getProperty(prefix + ".keyword4.bg")));
221: aStyles[Token.KEYWORD4].setFontStyle(parseFont(props
222: .getProperty(prefix + ".keyword4.font")));
223: aStyles[Token.KEYWORD5].setForeground(parseColor(props
224: .getProperty(prefix + ".keyword5.fg")));
225: aStyles[Token.KEYWORD5].setBackground(parseColor(props
226: .getProperty(prefix + ".keyword5.bg")));
227: aStyles[Token.KEYWORD5].setFontStyle(parseFont(props
228: .getProperty(prefix + ".keyword5.font")));
229: aStyles[Token.FUNCTION].setForeground(parseColor(props
230: .getProperty(prefix + ".function.fg")));
231: aStyles[Token.FUNCTION].setBackground(parseColor(props
232: .getProperty(prefix + ".function.bg")));
233: aStyles[Token.FUNCTION].setFontStyle(parseFont(props
234: .getProperty(prefix + ".function.font")));
235: aStyles[Token.OPERATOR].setForeground(parseColor(props
236: .getProperty(prefix + ".operator.fg")));
237: aStyles[Token.OPERATOR].setBackground(parseColor(props
238: .getProperty(prefix + ".operator.bg")));
239: aStyles[Token.OPERATOR].setFontStyle(parseFont(props
240: .getProperty(prefix + ".operator.font")));
241: aStyles[Token.MARKUP].setForeground(parseColor(props
242: .getProperty(prefix + ".markup.fg")));
243: aStyles[Token.MARKUP].setBackground(parseColor(props
244: .getProperty(prefix + ".markup.bg")));
245: aStyles[Token.MARKUP].setFontStyle(parseFont(props
246: .getProperty(prefix + ".markup.font")));
247: aStyles[Token.LABEL].setForeground(parseColor(props
248: .getProperty(prefix + ".label.fg")));
249: aStyles[Token.LABEL].setBackground(parseColor(props
250: .getProperty(prefix + ".label.bg")));
251: aStyles[Token.LABEL].setFontStyle(parseFont(props
252: .getProperty(prefix + ".label.font")));
253: aStyles[Token.INVALID].setForeground(parseColor(props
254: .getProperty(prefix + ".invalid.fg")));
255: aStyles[Token.INVALID].setBackground(parseColor(props
256: .getProperty(prefix + ".invalid.bg")));
257: aStyles[Token.INVALID].setFontStyle(parseFont(props
258: .getProperty(prefix + ".invalid.font")));
259: aStyles[Token.NULL].setFontStyle(parseFont(props
260: .getProperty(prefix + ".null.font")));
261: textPane.setForeground(parseColor(props.getProperty(prefix
262: + ".text.fg")));
263: textPane.setBackground(parseColor(props.getProperty(prefix
264: + ".text.bg")));
265: textPane.setCaretColor(parseColor(props.getProperty(prefix
266: + ".caret.fg")));
267: textPane.setBracketHighlightColor(parseColor(props
268: .getProperty(prefix + ".brackethighlight.fg")));
269: textPane.setSelectionColor(parseColor(props.getProperty(prefix
270: + ".selectioncolor.bg")));
271:
272: textPane.setLineHighlightColor(parseColor(props
273: .getProperty(prefix + ".linehighlight.fg")));
274: gutter.setForeground(parseColor(props.getProperty(prefix
275: + ".gutter.fg")));
276: gutter.setBackground(parseColor(props.getProperty(prefix
277: + ".gutter.bg")));
278: gutter.setDividerForeground(parseColor(props.getProperty(prefix
279: + ".divider.fg")));
280: gutter.setDividerBackground(parseColor(props.getProperty(prefix
281: + ".divider.bg")));
282: gutter.setCurrentLineForeground(parseColor(props
283: .getProperty(prefix + ".currentline.fg")));
284: gutter.setCurrentLineBackground(parseColor(props
285: .getProperty(prefix + ".currentline.bg")));
286: gutter.setLineIntervalForeground(parseColor(props
287: .getProperty(prefix + ".lineinterval.fg")));
288: gutter.setLineIntervalBackground(parseColor(props
289: .getProperty(prefix + ".lineinterval.bg")));
290: gutter.setSelectionForeground(parseColor(props
291: .getProperty(prefix + ".gutterselection.fg")));
292: gutter.setSelectionBackground(parseColor(props
293: .getProperty(prefix + ".gutterselection.bg")));
294: gutter.setBracketScopeForeground(parseColor(props
295: .getProperty(prefix + ".bracketscope.fg")));
296: gutter.setBracketScopeBackground(parseColor(props
297: .getProperty(prefix + ".bracketscope.bg")));
298: }
299:
300: private void reload() throws GUIException {
301: MapDataModel model = getDataModel();
302: if (model != null && key != null) {
303: try {
304: processEvents = false;
305: String value = (String) model.getValue(key);
306: if (value != null) {
307: textPane.setText(value);
308: } else {
309: model.setValue(SyntaxEditor.this , key, textPane
310: .getText());
311: }
312: } finally {
313: processEvents = true;
314: }
315: }
316: }
317:
318: public void modelChanged(ModelChangeEvent e) throws GUIException {
319: if (processEvents) {
320: sendEvents = false;
321: try {
322: if (e.getSource() == this ) {
323: /* New data model */
324: reload();
325: } else if (e instanceof MapChangeEvent) {
326: MapChangeEvent event = (MapChangeEvent) e;
327: if (event.getKey() == null) {
328: reload();
329: } else if (event.getKey().equals(key)) {
330: textPane.setText((String) event.getNewValue());
331: }
332: }
333: } finally {
334: sendEvents = true;
335: }
336: }
337: }
338:
339: public void finalizeConstruction() {
340: textPane.getDocument().addDocumentListener(
341: new DocumentListener() {
342: public void insertUpdate(DocumentEvent e) {
343: changedUpdate(e);
344: }
345:
346: public void removeUpdate(DocumentEvent e) {
347: changedUpdate(e);
348: }
349:
350: public void changedUpdate(DocumentEvent e) {
351: if (sendEvents) {
352: try {
353: MapDataModel model = getDataModel();
354: if (model != null && key != null) {
355: try {
356: processEvents = false;
357: model.setValue(
358: SyntaxEditor.this , key,
359: textPane.getText());
360: } finally {
361: processEvents = true;
362: }
363: }
364: } catch (GUIException ex) {
365: throw new RuntimeException(ex);
366: }
367: }
368: }
369: });
370: }
371:
372: public void setProperty(String name, Object value)
373: throws GUIException {
374: if ("mode".equals(name)) {
375: document.setSyntax(SyntaxFactory.getSyntax((String) value));
376: } else if ("key".equals(name)) {
377: key = (String) value;
378: } else if ("theme".equals(name)) {
379: setTheme(themes, (String) value);
380: } else if ("gutter".equals(name)) {
381: if (((Boolean) value).booleanValue()) {
382: scrollPane.setRowHeaderView(gutter);
383: scrollPane.setCorner(JScrollPane.LOWER_LEFT_CORNER,
384: gutterBase);
385: } else {
386: scrollPane.setRowHeader(null);
387: scrollPane.setCorner(JScrollPane.LOWER_LEFT_CORNER,
388: null);
389: }
390: } else if ("verticalScrollBar".equals(name)) {
391: scrollPane
392: .setVerticalScrollBarPolicy(((Boolean) value)
393: .booleanValue() ? JScrollPane.VERTICAL_SCROLLBAR_ALWAYS
394: : JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED);
395: } else if ("horizontalScrollBar".equals(name)) {
396: scrollPane
397: .setHorizontalScrollBarPolicy(((Boolean) value)
398: .booleanValue() ? JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS
399: : JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED);
400: } else {
401: super .setProperty(name, value);
402: }
403: }
404:
405: public String getText() {
406: return textPane.getText();
407: }
408:
409: public void setText(String text) {
410: textPane.setText(text);
411: }
412:
413: public Component getRealWidget() {
414: return textPane;
415: }
416:
417: public Component getWidget() {
418: return scrollPane;
419: }
420:
421: public WidgetInfo getWidgetInfo() {
422: return syntaxInfo;
423: }
424: }
|