/*
* See the NOTICE file distributed with this work for additional
* information regarding copyright ownership.
*
* This is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*/
package org.xwiki.gwt.wysiwyg.client.plugin.indent.exec;
import org.xwiki.gwt.dom.client.Element;
import org.xwiki.gwt.dom.client.Range;
import org.xwiki.gwt.user.client.ui.rta.RichTextArea;
import com.google.gwt.dom.client.Node;
/**
* Indent executable to handle valid XHTML lists indent, semantically: when a list item is indented, all its subitems
* are indented as well.
*
* @version $Id: 7edcd2a94d717c34c0ac1a23d93e6759c7a5130c $
*/
public class IndentExecutable extends AbstractListExecutable
{
/**
* Creates a new executable that can be used to indent list items inside the specified rich text area.
*
* @param rta the execution target
*/
public IndentExecutable(RichTextArea rta)
{
super(rta);
}
@Override
protected boolean executeOnMultipleItems(Range range, boolean perform)
{
// don't dive into this code unless you have a free afternoon, and definitely never before a release!
// indent all list items in the selection, whose ancestors are not to be indented, and make sure a list item is
// not indented twice.
// List items in the selection are detected as the li ancestors of all leaves in the selection, the indent is
// done on a given list item if:
// 1. it is not the descendant or self of the previously indented list item
// 2. it would not be the descendant or self of the previously indented list item after the indent execution
// the previously processed item is updated each time the first condition passes: it means that an indent was
// attempted on that list item
// assume none can be indented, and || on indent results
boolean indentResult = false;
// iterate through the leafs in the range
Node rangeLeaf = domUtils.getFirstLeaf(range);
Node lastLeaf = domUtils.getLastLeaf(range);
// store the last li on which an indent function was executed, regardless of the actual result of the indent
Element lastProcessed = null;
// check the ancestor li to indent for each leaf in the range
while (rangeLeaf != null) {
Element currentLi = (Element) domUtils.getFirstAncestor(rangeLeaf, LIST_ITEM_TAG);
// check the conditions: first condition, before indent
if (lastProcessed == null || (currentLi != null && !lastProcessed.isOrHasChild(currentLi))) {
// second condition translates into lastProcessedLi != currentItem.previousSibling, since indent is
// always performed relative to previous sibling.
if (lastProcessed == null || (currentLi != null && lastProcessed != currentLi.getPreviousSibling())) {
// perform indent and update function result. First the fct execution!
indentResult = checkAndPerformIndent(currentLi, perform) || indentResult;
}
// update the last item attempted to be handled, regardless of the actual result of the indent function
lastProcessed = currentLi;
}
// go to next leaf
rangeLeaf = getNextLeafBefore(rangeLeaf, lastLeaf);
}
return indentResult;
}
/**
* @param item the item to check and perform indent on
* @param perform if the indent needs to be actually performed or just checked
* @return {@code true} if the indent can be performed (and has been performed) or {@code false} otherwise
*/
private boolean checkAndPerformIndent(Element item, boolean perform)
{
boolean canIndent = canExecute(item);
// if can indent and should indent, indent
if (perform && canIndent) {
execute(item);
}
return canIndent;
}
/**
* Returns the next leaf before the passed leaf, including {@code lastLeaf}.
*
* @param currentLeaf the leaf to get the following leaf of
* @param lastLeaf the last leaf to be returned
* @return the following leaf after {@code currentLeaf}, before (an including) {@code lastLeaf}, or {@code null} if
* no more leaves exist to satisfy this condition
*/
private Node getNextLeafBefore(Node currentLeaf, Node lastLeaf)
{
if (currentLeaf == lastLeaf) {
return null;
} else {
return domUtils.getNextLeaf(currentLeaf);
}
}
@Override
protected boolean canExecute(Element listItem)
{
if (!super.canExecute(listItem)) {
return false;
}
// get the previous list item
Node previousListItem = listItem.getPreviousSibling();
if (previousListItem == null || !previousListItem.getNodeName().equalsIgnoreCase(LIST_ITEM_TAG)) {
return false;
}
return true;
}
/**
* {@inheritDoc}.
* <p>Performs the indent of the passed list item, if possible: it turns it into the child list item of
* its previous sibling, if such an element exists, also handling the merge if the previous sibling already has a
* sublist.</p>
*/
@Override
protected void execute(Element listItem)
{
Node previousListItem = listItem.getPreviousSibling();
// move it, inside the last list of the previous list item
Node lastChild = previousListItem.getLastChild();
Element lastSecondLevelList = null;
if (isList(lastChild)) {
lastSecondLevelList = (Element) lastChild;
}
// if there is no second level list, create a new list and add it to the previous list item
if (lastSecondLevelList == null) {
lastSecondLevelList =
(Element) listItem.getOwnerDocument().createElement(listItem.getParentNode().getNodeName());
previousListItem.appendChild(lastSecondLevelList);
}
// add the current list item as the last child of the lastSecondLevelList in the previous list item
lastSecondLevelList.appendChild(listItem);
}
}