/**
* 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 );
}
}