/** * Copyright 1999-2009 The Pegadi Team * * 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. */ /** * Base class for all editors. An editor's responsibility is to * provide a way to edit a portion of an XML-document, and to ensure * that this editing adheres to the documents DTD/Schema. * * @see org.pegadi.artis.Artis * @see org.pegadi.model.Article * @author HÃ¥vard Wigtil <havardw at pvv.org> * @version $Revision$, $Date$ */ package org.pegadi.artis; import com.kitfox.svg.SVGCache; import com.kitfox.svg.app.beans.SVGIcon; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.w3c.dom.Element; import javax.swing.*; import javax.swing.event.CaretEvent; import javax.swing.event.CaretListener; import javax.swing.text.Caret; import javax.swing.text.JTextComponent; import java.awt.*; import java.awt.event.ActionEvent; import java.awt.event.TextEvent; import java.awt.event.TextListener; import java.net.URI; import java.net.URL; import java.util.Locale; import java.util.ResourceBundle; public abstract class Editor extends JPanel { /** The XML element this object is editing. */ protected Element mElement; /** Action for cutting text. */ protected AbstractAction cutAction; /** Action for copying text. */ protected AbstractAction copyAction; /** Action for paste text. */ protected AbstractAction pasteAction; /** All user-visible strings. */ private ResourceBundle editorStrings; /** Global flag for edits */ protected boolean changedFlag; /** The Artis this editor is added in */ protected Artis artis; protected Logger log = LoggerFactory.getLogger(getClass()); /** * Creates a new editor with content. * This implementation calls <code>createUI</code> to create the interface * and translatable resources, and <code>setXML</code> with the * <code>xml</code> parameter. (In that order.)<p> * * * @param xml The element to edit. */ public Editor(Element xml, Artis artis) { this.artis = artis; createUI(Locale.getDefault()); setXML(xml); changedFlag = false; } /** * Creates an SVGIcon that can be used by the UI. * @param iconPath the path of the svg file * @return an SVGIcon */ protected SVGIcon initSvgIcon(URL iconPath) { URI iconURI = SVGCache.getSVGUniverse().loadSVG(iconPath); SVGIcon icon = new SVGIcon(); icon.setSvgURI(iconURI); icon.setAntiAlias(true); icon.setPreferredSize(new Dimension(16,16)); icon.setScaleToFit(true); return icon; } /** * Creates all user visible resources. * * @param loc The locale to use for the resources. */ protected void createUI(Locale loc) { editorStrings = ResourceBundle.getBundle("org.pegadi.artis.EditorStrings", loc); SVGIcon pasteIcon = initSvgIcon(getClass().getResource(editorStrings.getString("icon_paste"))); SVGIcon cutIcon = initSvgIcon(getClass().getResource(editorStrings.getString("icon_cut"))); SVGIcon copyIcon = initSvgIcon(getClass().getResource(editorStrings.getString("icon_copy"))); cutAction = new AbstractAction(editorStrings.getString("action_cut"), cutIcon) { //new ImageIcon(getClass().getResource(editorStrings.getString("icon_cut")))) { public void actionPerformed(ActionEvent e) { cutPerformed(e); } }; copyAction = new AbstractAction(editorStrings.getString("action_copy"), copyIcon) { //new ImageIcon(getClass().getResource(editorStrings.getString("icon_copy")))) { public void actionPerformed(ActionEvent e) { copyPerformed(e); } }; pasteAction = new AbstractAction(editorStrings.getString("action_paste"), pasteIcon) { //new ImageIcon(getClass().getResource(editorStrings.getString("icon_paste")))) { public void actionPerformed(ActionEvent e) { pastePerformed(e); } }; cutAction.putValue(AbstractAction.ACCELERATOR_KEY,javax.swing.KeyStroke.getKeyStroke(88, java.awt.event.KeyEvent.CTRL_MASK, false)); copyAction.putValue(AbstractAction.ACCELERATOR_KEY,javax.swing.KeyStroke.getKeyStroke(67, java.awt.event.KeyEvent.CTRL_MASK, false)); pasteAction.putValue(AbstractAction.ACCELERATOR_KEY,javax.swing.KeyStroke.getKeyStroke(86, java.awt.event.KeyEvent.CTRL_MASK, false)); cutAction.setEnabled(false); copyAction.setEnabled(false); pasteAction.setEnabled(false); } /** * Gets an updatet version of the element this editor is editing. * This will as a side-effect update this section in the main document. * * @return The updatet element */ public Element getXML() { updateXML(); return mElement; } /** * Sets the element to edit. * * @param xml The new element for the editor to use. */ public abstract void setXML(Element xml); /** * Standard method for all editors. Returns whether this editor needs to be saved. */ public boolean isChanged() { return changedFlag; } /** * Standard method for all editors. Set changeflag for this object */ public void setChanged(boolean status) { this.changedFlag = status; } /** * This method will update the main document with this editor's * content. */ public abstract void updateXML(); /** * Get the length of this editor. The length is the text in characters. * * @return The lenght */ public abstract int getLength(); /** * The name to display for this editor, according to the current locale. * * @return The Name. */ public abstract String getDisplayName(); /** * An icon to display as a symbol for this editor. May return <code>null</code>. * * @return Icon representing the editor. */ public abstract ImageIcon getDisplayIcon(); /** * Gets the <code>Action</code> that will insert a new item of this type. * This method may return <code>null</code> if it is not possible to add * items of this type. * * @return Action for adding a new item. */ public abstract Action getInsertAction(); /** * Gets the menu for this editor. May return <code>null</code>. * * @return The menu for this editor. */ public abstract JMenu getMenu(); /** * Returns editing actions for use in the edit menu and the toolbar. * The actions returned for this implementation is cut, copy and paste. * An editor should always return these actions, and rather disable the * actions if they are not applicable. * * @return Actions for editing. */ public AbstractAction[] getEditActions() { return new AbstractAction[]{ cutAction, copyAction, pasteAction}; } public AbstractAction[] getEditActions2(){ return new AbstractAction[0]; } public AbstractAction[] getStyleActions() { return new AbstractAction[0]; } public AbstractAction getAutoCorrectAction(){ return null; } public AbstractAction[] getFormatActions(){ return new AbstractAction[0]; } public AbstractAction[] getCharacterActions() { return new AbstractAction[0]; } /** * This method is triggered by the cut action. * * @param e The event for the action. * @see #getEditActions * @see #cutAction */ protected abstract void cutPerformed(ActionEvent e); /** * This method is triggered by the copy action. * * @param e The event for the action. * @see #getEditActions * @see #copyAction */ protected abstract void copyPerformed(ActionEvent e); /** * This method is triggered by the paste action. * * @param e The event for the action. * @see #getEditActions * @see #pasteAction */ protected abstract void pastePerformed(ActionEvent e); /** * Adds a text listener that will be notified when the text in this * editor changes. * * @param l The listener to add. */ public void addTextListener(TextListener l) { listenerList.add(TextListener.class, l); } public void addEasterListener(EasterListener l) { listenerList.add(EasterListener.class, l); } public void fireEaster() { /* * Easter is disabled. Maybe it will reappear for a later release. */ Object[] listeners = listenerList.getListenerList(); // Process the listeners last to first, notifying // those that are interested in this event for (int i = listeners.length-2; i>=0; i-=2) { if (listeners[i] == EasterListener.class) { ((EasterListener)listeners[i+1]).easterEventHappened(); } } } /** * Adds a caret listener that will be notified when text in this * editor is selected * @param listener The listener to add */ public void addCaretListener(CaretListener listener) { listenerList.add(CaretListener.class,listener); } /** * Remove a caret listener from this editor. * * @param listener The listener to remove. */ public void removeCaretListener(CaretListener listener) { listenerList.remove(CaretListener.class, listener); } /** * Remove a text listener from this editor. * * @param l The listener to remove. */ public void removeTextListener(TextListener l) { listenerList.remove(TextListener.class, l); } /** * Notifies all text listeners that the text has changed. */ protected void fireTextChanged() { log.debug("ABOUT TO FIRE TEXTLISTENERS."); setChanged(true); TextEvent te = new TextEvent(this, TextEvent.TEXT_VALUE_CHANGED); Object[] listeners = listenerList.getListenerList(); // Process the listeners last to first, notifying // those that are interested in this event for (int i = listeners.length-2; i>=0; i-=2) { if (listeners[i] == TextListener.class) { ((TextListener)listeners[i+1]).textValueChanged(te); } } } public void fireCaretUpdated(CaretEvent e) { Object[] listeners = listenerList.getListenerList(); // Process the listeners last to first, notifying // those that are interested in this event for (int i = listeners.length-2; i>=0; i-=2) { if (listeners[i] == CaretListener.class) { ((CaretListener)listeners[i+1]).caretUpdate(e); } } } public void disableEditor() { setEditorComponentsEnabled(this, false); cutAction.setEnabled(false); pasteAction.setEnabled(false); } /** * Alternative version of disableEditor() made to let you keep text selection and copy in locked article view. */ public void disableEditor_butKeepOptionsRelevantToTextCopying(){ setEditorComponentsEnabled_ExceptingCaretComponents(this, false); cutAction.setEnabled(false); pasteAction.setEnabled(false); } private void setEditorComponentsEnabled(Container container, boolean enabled) { if(container.getComponentCount() == 0) { return; } for(Component comp : container.getComponents()) { setEditorComponentsEnabled((Container) comp, enabled); if(comp instanceof JTextComponent) ((JTextComponent) comp ).setEditable(enabled); if(!(comp instanceof JScrollBar) ) { comp.setEnabled(enabled); } } } /** * Alternative version of setEditorComponentsEnabled(boolean) made to let you keep text selection * and copy in locked article view. * * @param container * @param enabled */ private void setEditorComponentsEnabled_ExceptingCaretComponents(Container container, boolean enabled){ if(container.getComponentCount() == 0) { return; } for(Component comp : container.getComponents()) { setEditorComponentsEnabled_ExceptingCaretComponents((Container) comp, enabled); if(comp instanceof JTextComponent && !isCaretObject(comp)){ ((JTextComponent) comp ).setEditable(enabled); } if(isCaretObject(comp)){ comp.setEnabled(true); } } } private boolean isCaretObject(Component c){ return ( c instanceof Caret || c instanceof CaretListener ); } }