/*
* 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.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Insets;
import java.awt.event.ActionListener;
import java.beans.EventHandler;
import java.util.List;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JSplitPane;
import javax.swing.JToolBar;
import javax.swing.JTree;
import javax.swing.ScrollPaneConstants;
import javax.swing.SwingConstants;
import javax.swing.event.TreeSelectionListener;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.DefaultTreeCellRenderer;
import javax.swing.tree.DefaultTreeModel;
import javax.swing.tree.TreeNode;
import javax.swing.tree.TreePath;
import javax.swing.tree.TreeSelectionModel;
import org.wikipediacleaner.api.data.PageAnalysis;
import org.wikipediacleaner.api.data.PageElementTitle;
import org.wikipediacleaner.gui.swing.basic.Utilities;
import org.wikipediacleaner.i18n.GT;
import org.wikipediacleaner.images.EnumImageSize;
/**
* A manager for the tree of titles.
*/
public class MWPaneTitleTreeManager {
private final MWPane textPane;
private final JSplitPane splitPane;
private final JTree treeToc;
private final DefaultTreeModel modelToc;
private final JTree treeToc2;
private final DefaultTreeModel modelToc2;
private boolean tocIsDisplayed;
/**
* Create the title tree manager.
*
* @param textPane Text pane.
*/
public MWPaneTitleTreeManager(MWPane textPane) {
// Text pane
this.textPane = textPane;
splitPane = new JSplitPane(JSplitPane.VERTICAL_SPLIT);
JScrollPane scrollContents = new JScrollPane(textPane);
scrollContents.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS);
splitPane.setBottomComponent(scrollContents);
// Table of contents
JPanel panelTOC = new JPanel(new GridBagLayout());
GridBagConstraints constraints = new GridBagConstraints();
constraints.fill = GridBagConstraints.BOTH;
constraints.gridheight = 1;
constraints.gridwidth = 1;
constraints.gridx = 0;
constraints.gridy = 0;
constraints.insets = new Insets(0, 0, 0, 0);
constraints.ipadx = 0;
constraints.ipady = 0;
constraints.weightx = 1;
constraints.weighty = 1;
// Toolbar
JToolBar toolbarButtons = new JToolBar(SwingConstants.VERTICAL);
toolbarButtons.setFloatable(false);
JButton buttonLess = Utilities.createJButton(
"gnome-go-previous.png", EnumImageSize.NORMAL,
GT._("Decrement title level"), false, null);
buttonLess.addActionListener(EventHandler.create(
ActionListener.class, this, "decreaseLevel"));
toolbarButtons.add(buttonLess);
JButton buttonMore = Utilities.createJButton(
"gnome-go-next.png", EnumImageSize.NORMAL,
GT._("Increment title level"), false, null);
buttonMore.addActionListener(EventHandler.create(
ActionListener.class, this, "increaseLevel"));
toolbarButtons.add(buttonMore);
JButton buttonDone = Utilities.createJButton(
"commons-approve-icon.png", EnumImageSize.NORMAL,
GT._("Validate the new table of contents"), false, null);
buttonDone.addActionListener(EventHandler.create(
ActionListener.class, this, "validate"));
toolbarButtons.add(buttonDone);
constraints.weightx = 0;
panelTOC.add(toolbarButtons, constraints);
constraints.gridx++;
// Tree node renderer
DefaultTreeCellRenderer rendererToc = new DefaultTreeCellRenderer();
rendererToc.setLeafIcon(rendererToc.getClosedIcon());
// Table of contents Tree
MWPaneTitleTreeNode rootToc = new MWPaneTitleTreeNode(null);
modelToc = new DefaultTreeModel(rootToc);
treeToc = new JTree(modelToc);
treeToc.setRootVisible(false);
treeToc.setShowsRootHandles(true);
treeToc.getSelectionModel().setSelectionMode(
TreeSelectionModel.CONTIGUOUS_TREE_SELECTION);
treeToc.setCellRenderer(rendererToc);
treeToc.addTreeSelectionListener(EventHandler.create(
TreeSelectionListener.class, this, "selectionChanged"));
JScrollPane scrollTree = new JScrollPane(treeToc);
scrollTree.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS);
constraints.weightx = 1;
panelTOC.add(scrollTree, constraints);
constraints.gridx++;
// Second tree
MWPaneTitleTreeNode rootToc2 = new MWPaneTitleTreeNode(null);
modelToc2 = new DefaultTreeModel(rootToc2);
treeToc2 = new JTree(modelToc2);
treeToc2.setRootVisible(false);
treeToc2.setShowsRootHandles(true);
treeToc2.setCellRenderer(rendererToc);
JScrollPane scrollTree2 = new JScrollPane(treeToc2);
scrollTree2.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS);
constraints.weightx = 1;
panelTOC.add(scrollTree2, constraints);
constraints.gridx++;
splitPane.setTopComponent(panelTOC);
// Hide table of contents
hideToc();
}
/**
* @return Component containing the trees.
*/
public JComponent getComponent() {
return splitPane;
}
/**
* Toggle display of table of contents.
*/
public void toggleToc() {
if (tocIsDisplayed) {
hideToc();
} else {
displayToc(null);
}
}
/**
* Display table of contents.
*
* @param title Title to be selected.
*/
public void displayToc(PageElementTitle title) {
if (!tocIsDisplayed) {
updateTreeToc();
updateTreeToc2();
selectTreeToc2();
tocIsDisplayed = true;
}
splitPane.setDividerLocation(200);
splitPane.setDividerSize(2);
splitPane.setResizeWeight(0.0);
textPane.setEditableInternal(false);
if (title != null) {
MWPaneTitleTreeNode node = findTreeNode(
(MWPaneTitleTreeNode) modelToc.getRoot(),
title);
if (node != null) {
TreePath treePath = new TreePath(node.getPath());
treeToc.scrollPathToVisible(treePath);
treeToc.setSelectionPath(treePath);
}
}
}
/**
* Hide table of contents.
*/
public void hideToc() {
tocIsDisplayed = false;
splitPane.setDividerLocation(0);
splitPane.setDividerSize(0);
splitPane.setResizeWeight(0.0);
textPane.setEditableInternal(textPane.isEditable);
}
/**
* Apply change in selection.
*/
public void selectionChanged() {
MWPaneTitleTreeNode treeNode = (MWPaneTitleTreeNode) treeToc.getLastSelectedPathComponent();
if (treeNode == null) {
return;
}
Object nodeInfo = treeNode.getUserObject();
if (nodeInfo instanceof PageElementTitle) {
PageElementTitle title = (PageElementTitle) nodeInfo;
try {
textPane.setCaretPosition(title.getBeginIndex());
textPane.moveCaretPosition(title.getEndIndex());
} catch (IllegalArgumentException e2) {
//
}
textPane.requestFocusInWindow();
}
selectTreeToc2();
}
/**
* Validate modifications.
*/
public void validate() {
StringBuilder contents = new StringBuilder(textPane.getText());
applyChanges(contents, treeToc.getModel().getRoot());
textPane.changeText(contents.toString());
treeToc.repaint();
textPane.requestFocusInWindow();
}
/**
* Increase level of the currently selected titles.
*/
public void increaseLevel() {
changeLevel(1);
}
/**
* Decrease level of the currently selected titles.
*/
public void decreaseLevel() {
changeLevel(-1);
}
/**
* Change level of the currently selected titles.
*
* @param delta Delta to apply.
*/
private void changeLevel(int delta) {
if (treeToc == null) {
return;
}
TreePath[] selections = treeToc.getSelectionPaths();
if (selections == null) {
return;
}
for (int i = 0; i < selections.length; i++) {
TreePath selection = selections[i];
boolean use = true;
for (int j = 0; j < i; j++) {
if (selections[j].isDescendant(selection)) {
use = false;
}
}
if (use) {
MWPaneTitleTreeNode treeNode = (MWPaneTitleTreeNode) selection.getLastPathComponent();
changeTitleLevel(treeNode, delta);
}
}
updateTreeToc2();
selectTreeToc2();
treeToc.repaint();
textPane.requestFocusInWindow();
}
/**
* Update table of contents tree.
*/
private void updateTreeToc() {
MWPaneTitleTreeNode rootNode = new MWPaneTitleTreeNode(null);
MWPaneTitleTreeNode lastNode = rootNode;
PageAnalysis pageAnalysis = textPane.getWikiPage().getAnalysis(textPane.getText(), true);
List<PageElementTitle> titles = pageAnalysis.getTitles();
for (PageElementTitle title : titles) {
while ((lastNode != null) &&
(lastNode.getInitialTitleLevel() >= title.getLevel())) {
if (lastNode.getParent() != null) {
lastNode = (MWPaneTitleTreeNode) lastNode.getParent();
} else {
lastNode = null;
}
}
if (lastNode == null) {
lastNode = rootNode;
}
MWPaneTitleTreeNode tmpNode = new MWPaneTitleTreeNode(title);
lastNode.add(tmpNode);
lastNode = tmpNode;
}
modelToc.setRoot(rootNode);
}
/**
* Update table of contents second tree.
*/
private void updateTreeToc2() {
MWPaneTitleTreeNode rootNode2 = new MWPaneTitleTreeNode(null);
MWPaneTitleTreeNode rootNode = (MWPaneTitleTreeNode) modelToc.getRoot();
updateTreeToc2Node(rootNode2, rootNode);
modelToc2.setRoot(rootNode2);
}
/**
* Select node in the second tree depending on the selection in the first tree.
*/
private void selectTreeToc2() {
MWPaneTitleTreeNode treeNode = (MWPaneTitleTreeNode) treeToc.getLastSelectedPathComponent();
if (treeNode == null) {
treeToc2.setSelectionRows(null);
} else {
MWPaneTitleTreeNode otherNode = findTreeNode(
(MWPaneTitleTreeNode) modelToc2.getRoot(),
treeNode.getTitle());
if (otherNode != null) {
TreePath treePath = new TreePath(otherNode.getPath());
treeToc2.scrollPathToVisible(treePath);
treeToc2.setSelectionPath(treePath);
}
}
}
/**
* Find a tree node matching a title.
*
* @param node Current node.
* @param title Title.
* @return Node matching the title if found.
*/
private MWPaneTitleTreeNode findTreeNode(
MWPaneTitleTreeNode node, PageElementTitle title) {
if (node == null) {
return null;
}
if (title == node.getTitle()) {
return node;
}
for (int i = 0; i < node.getChildCount(); i++) {
MWPaneTitleTreeNode found = findTreeNode(
(MWPaneTitleTreeNode) node.getChildAt(i), title);
if (found != null) {
return found;
}
}
return null;
}
/**
* Update table of contents second tree for a node and its children.
*
* @param rootNode2 Root of second tree.
* @param node Current node to add.
*/
private void updateTreeToc2Node(
MWPaneTitleTreeNode rootNode2,
MWPaneTitleTreeNode node) {
if (node == null) {
return;
}
for (int i = 0; i < node.getChildCount(); i++) {
MWPaneTitleTreeNode currentNode = (MWPaneTitleTreeNode) node.getChildAt(i);
MWPaneTitleTreeNode newNode = new MWPaneTitleTreeNode(currentNode.getTitle());
newNode.setCurrentTitleLevel(currentNode.getCurrentTitleLevel());
MWPaneTitleTreeNode parentNode = (MWPaneTitleTreeNode) rootNode2.getLastLeaf();
while ((parentNode.isRoot() == false) &&
(parentNode.getCurrentTitleLevel() >= newNode.getCurrentTitleLevel())) {
parentNode = (MWPaneTitleTreeNode) parentNode.getParent();
}
parentNode.add(newNode);
updateTreeToc2Node(rootNode2, currentNode);
}
}
/**
* Change title level (including children).
*
* @param treeNode Node.
* @param increment Increment.
*/
private void changeTitleLevel(MWPaneTitleTreeNode treeNode, int increment) {
if (treeNode.getCurrentTitleLevel() + increment > 0) {
treeNode.setCurrentTitleLevel(treeNode.getCurrentTitleLevel() + increment);
for (int i = 0; i < treeNode.getChildCount(); i++) {
TreeNode child = treeNode.getChildAt(i);
if (child instanceof MWPaneTitleTreeNode) {
changeTitleLevel((MWPaneTitleTreeNode) treeNode.getChildAt(i), increment);
}
}
}
}
/**
* Save changes to table of contents.
*
* @param contents Contents.
* @param node Node.
*/
private void applyChanges(StringBuilder contents, Object node) {
if ((contents == null) || (node == null)) {
return;
}
if (!(node instanceof MWPaneTitleTreeNode)) {
return;
}
MWPaneTitleTreeNode treeNode = (MWPaneTitleTreeNode) node;
for (int i = treeNode.getChildCount(); i > 0; i--) {
applyChanges(contents, treeNode.getChildAt(i - 1));
}
if (treeNode.getCurrentTitleLevel() != treeNode.getInitialTitleLevel()) {
StringBuilder newTitle = new StringBuilder();
for (int i = 0; i < treeNode.getCurrentTitleLevel(); i++) {
newTitle.append("=");
}
newTitle.append(" ");
newTitle.append(treeNode.getTitle().getTitle());
newTitle.append(" ");
for (int i = 0; i < treeNode.getCurrentTitleLevel(); i++) {
newTitle.append("=");
}
contents.replace(
treeNode.getTitle().getBeginIndex(),
treeNode.getTitle().getEndIndex(),
newTitle.toString());
textPane.hideToc();
textPane.resetAttributes();
textPane.requestFocusInWindow();
}
}
}
/**
* A tree node for titles.
*/
class MWPaneTitleTreeNode extends DefaultMutableTreeNode {
private static final long serialVersionUID = 1L;
private final PageElementTitle title;
private int level;
/**
* @param title Title.
*/
public MWPaneTitleTreeNode(PageElementTitle title) {
super((title != null) ? title : "Page");
this.title = title;
this.level = (title != null) ? title.getLevel() : 0;
}
/**
* @return Title level.
*/
public int getInitialTitleLevel() {
if (title != null) {
return title.getLevel();
}
return 0;
}
/**
* @return Title level.
*/
public int getCurrentTitleLevel() {
return level;
}
/**
* @param level Title level.
*/
public void setCurrentTitleLevel(int level) {
this.level = level;
}
/**
* @return Title.
*/
public PageElementTitle getTitle() {
return title;
}
/* (non-Javadoc)
* @see javax.swing.tree.DefaultMutableTreeNode#toString()
*/
@Override
public String toString() {
if (title == null) {
return super.toString();
}
StringBuilder buffer = new StringBuilder();
buffer.append("(");
buffer.append(title.getLevel() - 1);
if (title.getLevel() != level) {
buffer.append(" -> ");
buffer.append(level - 1);
}
buffer.append(") ");
buffer.append(title.getTitle());
return buffer.toString();
}
}