/*
* WPCleaner: A tool to help on Wikipedia maintenance tasks.
* Copyright (C) 2013 Nicolas Vervelle
*
* See README.txt file for licensing information.
*/
package org.wikipediacleaner.gui.swing.component;
import java.awt.Font;
import java.awt.GraphicsEnvironment;
import java.awt.event.InputEvent;
import java.awt.event.KeyEvent;
import java.util.ArrayList;
import java.util.List;
import javax.swing.AbstractButton;
import javax.swing.ActionMap;
import javax.swing.InputMap;
import javax.swing.JComponent;
import javax.swing.JTextPane;
import javax.swing.KeyStroke;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import javax.swing.text.StyledDocument;
import org.wikipediacleaner.api.constants.EnumWikipedia;
import org.wikipediacleaner.api.data.Page;
import org.wikipediacleaner.api.data.PageAnalysis;
import org.wikipediacleaner.api.data.PageElementTitle;
import org.wikipediacleaner.gui.swing.action.FindTextAction;
import org.wikipediacleaner.gui.swing.action.ReplaceLinkAction;
import org.wikipediacleaner.gui.swing.basic.BasicWindow;
/**
* A text component to color / edit MediaWiki text.
*/
public class MWPane
extends JTextPane {
private static final long serialVersionUID = 3225120886653438117L;
public static final String PROPERTY_MODIFIED = "ModifiedProperty";
private final EnumWikipedia wikipedia;
private Page page;
private final BasicWindow window;
private MWPaneFormatter formatter;
private static final KeyStroke lastLinkKeyStroke = KeyStroke.getKeyStroke(KeyEvent.VK_L, InputEvent.CTRL_MASK);
private static final KeyStroke lastReplaceKeyStroke = KeyStroke.getKeyStroke(KeyEvent.VK_R, InputEvent.CTRL_MASK);
private boolean isModified = false;
boolean isEditable = true;
boolean isInInternalModification = false;
private transient MWPanePopupListener popupListener;
public static KeyStroke getLastLinkKeyStroke() {
return lastLinkKeyStroke;
}
public static KeyStroke getLastReplaceKeyStroke() {
return lastReplaceKeyStroke;
}
/**
* Construct a MediaWikiPane.
*
* @param wikipedia Wikipedia.
* @param page Page.
* @param window Window containing the pane.
*/
public MWPane(EnumWikipedia wikipedia, Page page, BasicWindow window) {
super();
this.wikipedia = wikipedia;
this.page = page;
this.window = window;
this.selectionManager = new MWPaneSelectionManager(this);
this.undoManager = new MWPaneUndoManager(this);
this.formatter = new MWPaneBasicFormatter();
initialize();
}
/**
* @param page Page.
*/
public void setWikiPage(Page page) {
this.page = page;
}
/**
* @return page Page.
*/
public Page getWikiPage() {
return page;
}
/**
* @return Wikipedia
*/
public EnumWikipedia getWikipedia() {
return wikipedia;
}
/**
* @return Flag indicating if the document has been modified.
*/
public boolean isModified() {
return isModified;
}
/**
* @param modified New status of the document
*/
public void setModified(boolean modified) {
if (isModified != modified) {
boolean oldValue = isModified;
isModified = modified;
undoManager.updateUndoButtons();
firePropertyChange(PROPERTY_MODIFIED, oldValue, isModified);
}
}
/**
* Initialize styles.
*/
private void initialize() {
boolean oldState = isInInternalModification;
isInInternalModification = true;
this.setComponentOrientation(wikipedia.getSettings().getComponentOrientation());
StyledDocument doc = MWPaneFormatter.createDocument();
setStyledDocument(doc);
doc.addDocumentListener(new DocumentListener() {
/* (non-Javadoc)
* @see javax.swing.event.DocumentListener#changedUpdate(javax.swing.event.DocumentEvent)
*/
@Override
public void changedUpdate(@SuppressWarnings("unused") DocumentEvent e) {
changeDocument();
}
/* (non-Javadoc)
* @see javax.swing.event.DocumentListener#insertUpdate(javax.swing.event.DocumentEvent)
*/
@Override
public void insertUpdate(@SuppressWarnings("unused") DocumentEvent e) {
changeDocument();
}
/* (non-Javadoc)
* @see javax.swing.event.DocumentListener#removeUpdate(javax.swing.event.DocumentEvent)
*/
@Override
public void removeUpdate(@SuppressWarnings("unused") DocumentEvent e) {
changeDocument();
}
public void changeDocument() {
if (!isModified() && !isInInternalModification) {
setModified(true);
}
}
});
ActionMap actionMap = getActionMap();
InputMap inputMapFocused = getInputMap();
InputMap inputMapInFocused = getInputMap(WHEN_IN_FOCUSED_WINDOW);
KeyStroke keyStroke = null;
setPopupListener(new MWPaneBasicPopupListener(wikipedia, window));
keyStroke = KeyStroke.getKeyStroke(KeyEvent.VK_F, InputEvent.CTRL_MASK);
inputMapFocused.put(keyStroke, "find-text");
actionMap.put("find-text", new FindTextAction());
inputMapInFocused.put(lastLinkKeyStroke, "last-link");
actionMap.put("last-link", new ReplaceLinkAction(false));
inputMapInFocused.put(lastReplaceKeyStroke, "last-replace");
actionMap.put("last-replace", new ReplaceLinkAction(true));
isInInternalModification = oldState;
setModified(false);
}
/**
* @param listener Popup listener.
*/
public void setPopupListener(MWPanePopupListener listener) {
// Remove old listener
if (popupListener != null) {
removeKeyListener(popupListener);
removeMouseListener(popupListener);
}
// Add new listener
popupListener = listener;
if (popupListener != null) {
addKeyListener(popupListener);
addMouseListener(popupListener);
}
}
/* (non-Javadoc)
* @see javax.swing.JEditorPane#setText(java.lang.String)
*/
@Override
public void setText(String t) {
setTextInternal(t, true, true);
}
/**
* Change text.
*
* @param t New text.
*/
public void changeText(String t) {
if (t == null) {
return;
}
String oldText = getText();
if (!oldText.equals(t)) {
setTextInternal(t, false, false);
setModified(true);
}
}
/**
* Enabling changing text without resetting the modified flag.
*
* @param t New text.
* @param resetModified Flag indicating if the modified flag should be reseted.
* @param validate Flag indicating if the text should be validated.
*/
private void setTextInternal(
String t,
boolean resetModified,
boolean validate) {
boolean oldState = isInInternalModification;
isInInternalModification = true;
super.setText(t);
setCaretPosition(0);
moveCaretPosition(0);
resetAttributes();
isInInternalModification = oldState;
if (resetModified) {
setModified(false);
undoManager.clear();
}
if (validate) {
undoManager.validateCurrentText();
}
}
@Override
public void setEditable(boolean editable) {
setEditableInternal(editable);
this.isEditable = editable;
}
/**
* Enabling to render this component editable or not temporarily.
*
* @param editable
*/
void setEditableInternal(boolean editable) {
super.setEditable(editable);
}
/* ========================================================================= */
/* Selection management */
/* ========================================================================= */
/**
* Selection manager.
*/
private final MWPaneSelectionManager selectionManager;
/**
* @return Selection manager.
*/
public MWPaneSelectionManager getSelectionManager() {
return selectionManager;
}
/* ========================================================================= */
/* Formatting management */
/* ========================================================================= */
/**
* @param formatter Formatter.
*/
public void setFormatter(MWPaneFormatter formatter) {
this.formatter = formatter;
resetAttributes();
}
/**
* @return Formatter.
*/
public MWPaneFormatter getFormatter() {
return formatter;
}
/**
* Reset attributes of the document.
* This method should be called after modifications are done.
*/
public void resetAttributes() {
// Check formatter
if (formatter == null) {
return;
}
boolean oldState = isInInternalModification;
isInInternalModification = true;
// First remove MediaWiki styles
String contents = getText();
PageAnalysis pageAnalysis = (page != null) ? page.getAnalysis(contents, true) : null;
formatter.format(this, pageAnalysis);
isInInternalModification = oldState;
if (!isInInternalModification) {
undoManager.validateCurrentText();
}
}
/* ========================================================================= */
/* Font management */
/* ========================================================================= */
/**
* @return Flag indicating if all the text can be displayed.
*/
public boolean canDisplayAllText() {
String text = getText();
Font font = getFont();
if ((text != null) && (font != null)) {
return (font.canDisplayUpTo(text) == -1);
}
return true;
}
/**
* @return List of fonts that can display all characters.
*/
public List<Font> getPossibleFonts() {
String text = getText();
List<Font> possibleFonts = new ArrayList<Font>();
Font[] allFonts = GraphicsEnvironment.getLocalGraphicsEnvironment().getAllFonts();
for (int i = 0; i < allFonts.length; i++) {
if (allFonts[i].canDisplayUpTo(text) == -1) {
possibleFonts.add(allFonts[i]);
}
}
return possibleFonts;
}
/* ========================================================================= */
/* Check Box management */
/* ========================================================================= */
private AbstractButton chkAddNote;
private AbstractButton chkCreateDabWarning;
private AbstractButton chkUpdateDabWarning;
/**
* @return Button used for adding a note in the Talk page.
*/
public AbstractButton getCheckBoxAddNote() {
return chkAddNote;
}
/**
* @param chk Button used for adding a note in the Talk page.
*/
public void setCheckBoxAddNote(AbstractButton chk) {
chkAddNote = chk;
}
/**
* @return Button used for creating disambiguation warning in the Talk page.
*/
public AbstractButton getCheckBoxCreateDabWarning() {
return chkCreateDabWarning;
}
/**
* @param chk Button used for creating disambiguation warning in the Talk page.
*/
public void setCheckBoxCreateDabWarning(AbstractButton chk) {
chkCreateDabWarning = chk;
}
/**
* @return Button used for updating disambiguation warning in the Talk page.
*/
public AbstractButton getCheckBoxUpdateDabWarning() {
return chkUpdateDabWarning;
}
/**
* @param chk Button used for updating disambiguation warning in the Talk page.
*/
public void setCheckBoxUpdateDabWarning(AbstractButton chk) {
chkUpdateDabWarning = chk;
}
/* ========================================================================= */
/* Undo / Redo management */
/* ========================================================================= */
/**
* Undo / Redo management.
*/
private final MWPaneUndoManager undoManager;
/**
* @return Undo / Redo management.
*/
public MWPaneUndoManager getUndoManager() {
return undoManager;
}
/* ========================================================================= */
/* Complex Pane management */
/* ========================================================================= */
private MWPaneTitleTreeManager treeManager;
/**
* Construct a complex MediaWikiPane.
*
* @param textPane Existing MediaWikiPane.
* @return Complex component containing a MediaWikiPane.
*/
public static JComponent createComplexPane(
final MWPane textPane) {
if (textPane == null) {
return null;
}
if (textPane.treeManager == null) {
textPane.treeManager = new MWPaneTitleTreeManager(textPane);
}
return textPane.treeManager.getComponent();
}
/**
* Display or hide Table of Contents.
*/
public void toggleToc() {
if (treeManager != null) {
treeManager.toggleToc();
}
}
/**
* Display Table of Contents.
*
* @param title Title to be selected.
*/
public void displayToc(PageElementTitle title) {
if (treeManager != null) {
treeManager.displayToc(title);
}
}
/**
* Hide Table of Contents.
*/
public void hideToc() {
if (treeManager != null) {
treeManager.hideToc();
}
}
}