/* * Copyright 2008 Ayman Al-Sairafi ayman.alsairafi@gmail.com * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License * at http://www.apache.org/licenses/LICENSE-2.0 * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package jsyntaxpane; import java.awt.Color; import java.util.logging.Level; import java.awt.Font; import java.awt.GraphicsEnvironment; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Properties; import java.util.Set; import java.util.logging.Logger; import java.util.regex.Pattern; import javax.swing.JEditorPane; import javax.swing.KeyStroke; import javax.swing.text.DefaultEditorKit; import javax.swing.text.Document; import javax.swing.text.Element; import javax.swing.text.JTextComponent; import javax.swing.text.Keymap; import javax.swing.text.TextAction; import javax.swing.text.View; import javax.swing.text.ViewFactory; import jsyntaxpane.actions.SyntaxAction; import jsyntaxpane.components.SyntaxComponent; import jsyntaxpane.util.Configuration; import jsyntaxpane.util.JarServiceProvider; /** * The DefaultSyntaxKit is the main entry to SyntaxPane. To use the package, just * set the EditorKit of the EditorPane to a new instance of this class. * * You need to pass a proper lexer to the class. * * @author ayman */ public class DefaultSyntaxKit extends DefaultEditorKit implements ViewFactory { public static Font DEFAULT_FONT; private static Set<String> CONTENTS = new HashSet<String>(); private static boolean initialized = false; private Lexer lexer; private static final Logger LOG = Logger.getLogger(DefaultSyntaxKit.class.getName()); public static final Pattern COMMA_REGEX = Pattern.compile("\\w+,\\w+"); private List<SyntaxComponent> editorComponents = new ArrayList<SyntaxComponent>(); private Map<String, SyntaxAction> editorActions = new HashMap<String, SyntaxAction>(); /** * Main Configuration of JSyntaxPane */ private static Configuration CONFIG; static { initKit(); } /** * Create a new Kit for the given language * @param lexer */ public DefaultSyntaxKit(Lexer lexer) { super(); this.lexer = lexer; } @Override public ViewFactory getViewFactory() { return this; } @Override public View create(Element element) { return new SyntaxView(element, CONFIG, this.getClass().getSimpleName()); } /** * Install the View on the given EditorPane. This is called by Swing and * can be used to do anything you need on the JEditorPane control. Here * I set some default Actions. * * @param editorPane */ @Override public void install(JEditorPane editorPane) { super.install(editorPane); editorPane.setFont(DEFAULT_FONT); Keymap km_parent = JTextComponent.getKeymap(JTextComponent.DEFAULT_KEYMAP); Keymap km_new = JTextComponent.addKeymap(null, km_parent); String kitName = this.getClass().getSimpleName(); Color caretColor = CONFIG.getPrefixColor(kitName, "CaretColor", Color.BLACK); editorPane.setCaretColor(caretColor); addSyntaxActions(km_new, kitName); editorPane.setKeymap(km_new); // install the components to the editor: String[] components = CONFIG.getPrefixPropertyList(kitName, "Components"); for (String c : components) { try { @SuppressWarnings("unchecked") Class<SyntaxComponent> compClass = (Class<SyntaxComponent>) Class.forName(c); SyntaxComponent comp = compClass.newInstance(); comp.config(CONFIG, kitName); comp.install(editorPane); editorComponents.add(comp); } catch (InstantiationException ex) { LOG.log(Level.SEVERE, null, ex); } catch (IllegalAccessException ex) { LOG.log(Level.SEVERE, null, ex); } catch (ClassNotFoundException ex) { LOG.log(Level.SEVERE, null, ex); } } } @Override public void deinstall(JEditorPane editorPane) { for (SyntaxComponent c : editorComponents) { c.deinstall(editorPane); } editorComponents.clear(); } /** * Add keyboard actions to this control using the Configuration we have * @param map * @param prefix */ public void addSyntaxActions(Keymap map, String prefix) { // look at all keys that either start with prefix.Action, or // that start with Action. Configuration actionsConf = CONFIG.subConfig(prefix, "Action."); for (String actionName : actionsConf.stringPropertyNames()) { String[] values = Configuration.COMMA_SEPARATOR.split( actionsConf.getProperty(actionName)); String actionClass = values[0]; SyntaxAction action = editorActions.get(actionClass); if (action == null) { action = createAction(actionClass); action.config(CONFIG, prefix, actionName); } String keyStrokeString = values[1]; KeyStroke ks = KeyStroke.getKeyStroke(keyStrokeString); // KeyEvent.VK_QUOTEDBL if (ks == null) { throw new IllegalArgumentException("Invalid KeyStroke: " + keyStrokeString); } TextAction ta = action.getAction(actionName); if(ta == null) { throw new IllegalArgumentException("Invalid ActionName: " + actionName); } map.addActionForKeyStroke(ks, ta); } } private SyntaxAction createAction(String actionClassName) { SyntaxAction action = null; try { Class clazz = Class.forName(actionClassName); action = (SyntaxAction) clazz.newInstance(); editorActions.put(actionClassName, action); } catch (InstantiationException ex) { throw new IllegalArgumentException("Cannot create action class: " + actionClassName, ex); } catch (IllegalAccessException ex) { throw new IllegalArgumentException("Cannot create action class: " + actionClassName, ex); } catch (ClassNotFoundException ex) { throw new IllegalArgumentException("Cannot create action class: " + actionClassName, ex); } catch (ClassCastException ex) { throw new IllegalArgumentException("Cannot create action class: " + actionClassName, ex); } return action; } /** * This is called by Swing to create a Document for the JEditorPane document * This may be called before you actually get a reference to the control. * We use it here to create a proper lexer and pass it to the * SyntaxDcument we return. * @return */ @Override public Document createDefaultDocument() { return new SyntaxDocument(lexer); } /** * This is called to initialize the list of <code>Lexer</code>s we have. * You can call this at initialization, or it will be called when needed. * The method will also add the appropriate EditorKit classes to the * corresponding ContentType of the JEditorPane. After this is called, * you can simply call the editor.setCOntentType("text/java") on the * control and you will be done. */ public static void initKit() { // attempt to find a suitable default font CONFIG = new Configuration(JarServiceProvider.readProperties("jsyntaxpane.config")); GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment(); String[] fonts = ge.getAvailableFontFamilyNames(); Arrays.sort(fonts); if (Arrays.binarySearch(fonts, "Courier new") >= 0) { DEFAULT_FONT = new Font("Courier New", Font.PLAIN, 12); } else if (Arrays.binarySearch(fonts, "Courier") >= 0) { DEFAULT_FONT = new Font("Courier", Font.PLAIN, 12); } else if (Arrays.binarySearch(fonts, "Monospaced") >= 0) { DEFAULT_FONT = new Font("Monospaced", Font.PLAIN, 13); } // read the Default Kits and their associated types Properties kitsForTypes = JarServiceProvider.readProperties("jsyntaxpane.kitsfortypes"); for (String type : kitsForTypes.stringPropertyNames()) { String classname = kitsForTypes.getProperty(type); registerContentType(type, classname); } initialized = true; } /** * Register the given content type to use the given class name as its kit * When this is called, an entry is added into the private HashMap of the * registered editors kits. This is needed so that the SyntaxPane library * has it's own registration of all the EditorKits * @param type * @param classname */ public static void registerContentType(String type, String classname) { JEditorPane.registerEditorKitForContentType(type, classname); CONTENTS.add(type); } /** * Return all the content types supported by this library. This will be the * content types in the file WEB-INF/services/resources/jsyntaxpane.kitsfortypes * @return sorted array of all registered content types */ public static String[] getContentTypes() { String[] types = CONTENTS.toArray(new String[0]); Arrays.sort(types); return types; } /** * returns the current config * @return */ public static Configuration getConfig() { if (!initialized) { initKit(); } return CONFIG; } /** * Merges the given properties with the defaults, which are read from the * Jar file * @param config */ public static void setConfig(Properties config) { DefaultSyntaxKit.CONFIG.putAll(config); } /** * Sets the given property to the given value. If the kit is not * initialized, then calls initKit * @param key * @param value */ public static void setProperty(String key, String value) { if (!initialized) { initKit(); } CONFIG.put(key, value); } /** * Return the property with the given key. If the kit is not * initialized, then calls initKit * Be careful when changing property as the default property may be used * @param key * @return value for given key */ public static String getProperty(String key) { if (!initialized) { initKit(); } return CONFIG.getProperty(key); } }