/* * 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.dom.client.Selection; import org.xwiki.gwt.user.client.ui.rta.RichTextArea; import org.xwiki.gwt.user.client.ui.rta.cmd.internal.AbstractSelectionExecutable; import com.google.gwt.dom.client.Node; /** * Superclass for all the list executables, such as indent and outdent executables, to handle frequent list operations. * * @version $Id: 25f08246925c20a27ebb3094a129e0cd53f54fb9 $ */ public abstract class AbstractListExecutable extends AbstractSelectionExecutable { /** * List item element name. */ protected static final String LIST_ITEM_TAG = "li"; /** * Unordered list element name. */ protected static final String UNORDERED_LIST_TAG = "ul"; /** * Ordered list element name. */ protected static final String ORDERED_LIST_TAG = "ol"; /** * Creates a new executable to be executed on the specified rich text area. * * @param rta the execution target */ public AbstractListExecutable(RichTextArea rta) { super(rta); } /** * @param range a DOM range * @return the list item in which the given range is positioned, or {@code null} if no such thing exists */ protected Element getListItem(Range range) { return (Element) domUtils.getFirstAncestor(range.getCommonAncestorContainer(), LIST_ITEM_TAG); } /** * Checks if the passed node is a list node: either an ordered list or an unordered one. If the passed node is null, * false is returned. * * @param node the node to check if it's a list or not * @return true if the passed node is a list node, false otherwise (including the case when the node is null). */ protected boolean isList(Node node) { return node != null && (node.getNodeName().equalsIgnoreCase(ORDERED_LIST_TAG) || node.getNodeName().equalsIgnoreCase( UNORDERED_LIST_TAG)); } @Override public boolean execute(String param) { boolean executionResult = false; Selection selection = rta.getDocument().getSelection(); Range range = selection.getRangeAt(0); if (range.isCollapsed()) { Element listItem = getListItem(range); if (canExecute(listItem)) { execute(listItem); executionResult = true; } } else { executionResult = executeOnMultipleItems(range, true); } // try to restore selection, hope it all stays well selection.removeAllRanges(); selection.addRange(range); return executionResult; } /** * Actually executes the operation on a single list item. This should be called only after * {@link AbstractListExecutable#canExecute(Element)} on the same list item returns true. * * @param listItem the list item to execute the operation on */ protected abstract void execute(Element listItem); /** * Checks if this command can be executed on a single list item. * * @param listItem the list item to check if the command can be executed on * @return {@code true} if the command can be executed, {@code false} otherwise */ protected boolean canExecute(Element listItem) { return listItem != null; } /** * Executes this list operation on all items in the non-collapsed selection. The {@code perform} parameter specifies * if the operation is actually performed or just checked to be possible (while this kind of parameters are not good * practice, it's the best way right now to make sure we use the same detection algorithm in the * {@link #execute(String)} and {@link #isEnabled()} functions). * * @param range the current range to execute the operation on * @param perform {@code true} if the operation is to be actually executed, {@code false} if it's only to be checked * @return {@code true} if at least one of the items in the selection was affected, {@code false} otherwise. */ protected abstract boolean executeOnMultipleItems(Range range, boolean perform); @Override public boolean isEnabled() { if (!super.isEnabled()) { return false; } // Get the range and check if execution is possible: if it's collapsed, it's the common list item ancestor to // perform operation on, if it's expanded, it's each "touched" list item. Range range = rta.getDocument().getSelection().getRangeAt(0); if (range.isCollapsed()) { Element listItem = getListItem(range); return canExecute(listItem); } else { // Check the execution is possible on multiple items, without actually performing it. return executeOnMultipleItems(range, false); } } }