/* * 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.style.exec; import java.util.Arrays; import java.util.Collections; import java.util.HashSet; import java.util.Set; 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.StringUtils; import org.xwiki.gwt.user.client.ui.rta.RichTextArea; import org.xwiki.gwt.user.client.ui.rta.cmd.internal.BlockStyleExecutable; import com.google.gwt.dom.client.Node; /** * Applies a given style name to each of the block nodes touched by the current text selection. * * @version $Id: e42316bc0249917f98433e68a2bbca676157b450 $ */ public class BlockStyleNameExecutable extends BlockStyleExecutable { /** * Flag indicating if this executable has been executed on the current selection with the given parameter. */ private boolean executed; /** * Creates a new instance. * * @param rta the execution target */ public BlockStyleNameExecutable(RichTextArea rta) { super(rta, null); } @Override public boolean execute(String parameter) { executed = getStyleNames(rta.getDocument().getSelection()).contains(parameter); return super.execute(parameter); } @Override protected void execute(Node node, int startOffset, int endOffset, String value) { if (executed) { removeStyleName(node, value); } else if (!matchesStyleName(node, value)) { super.execute(node, startOffset, endOffset, value); } } @Override protected void addStyle(Element element, String styleName) { element.addClassName(styleName); } /** * Removes the specified style name from the block-level ancestors of the given DOM node. * * @param node a DOM node * @param styleName the style name to be removed */ protected void removeStyleName(Node node, String styleName) { Node ancestor = domUtils.getNearestBlockContainer(node); while (ancestor != null && ancestor.getNodeType() == Node.ELEMENT_NODE) { Element.as(ancestor).removeClassName(styleName); ancestor = ancestor.getParentNode(); } } /** * @param node a DOM node * @param styleName a style name * @return {@code true} if any of the block-level ancestors of the given node has the specified style name, {@code * false} otherwise */ protected boolean matchesStyleName(Node node, String styleName) { Node ancestor = domUtils.getNearestBlockContainer(node); while (ancestor != null && ancestor.getNodeType() == Node.ELEMENT_NODE) { if (Element.as(ancestor).hasClassName(styleName)) { return true; } ancestor = ancestor.getParentNode(); } return false; } @Override public String getParameter() { return StringUtils.join(getStyleNames(rta.getDocument().getSelection()), " "); } /** * @param selection a text selection * @return the set of style names that affect all the text nodes touched by the given text selection */ protected Set<String> getStyleNames(Selection selection) { Set<String> selectionStyleNames = null; for (int i = 0; i < selection.getRangeCount(); i++) { Set<String> rangeStyleNames = getStyleNames(selection.getRangeAt(i)); if (selectionStyleNames == null) { selectionStyleNames = rangeStyleNames; } else { selectionStyleNames.retainAll(rangeStyleNames); } if (selectionStyleNames.isEmpty()) { break; } } if (selectionStyleNames == null) { selectionStyleNames = Collections.emptySet(); } return selectionStyleNames; } /** * @param range a text range * @return the set of style names that affect all the leafs in the given DOM range */ protected Set<String> getStyleNames(Range range) { Node leaf = domUtils.getFirstLeaf(range); if (leaf == null) { // The range is collapsed between nodes. return getStyleNames(range.getStartContainer()); } else { Node lastLeaf = domUtils.getLastLeaf(range); Set<String> rangeStyleNames = getStyleNames(leaf); while (leaf != lastLeaf && !rangeStyleNames.isEmpty()) { leaf = domUtils.getNextLeaf(leaf); rangeStyleNames.retainAll(getStyleNames(leaf)); } return rangeStyleNames; } } /** * @param node a DOM node * @return the set of style names that affect the block-level ancestors of the given DOM node */ protected Set<String> getStyleNames(Node node) { Set<String> styleNames = new HashSet<String>(); Node ancestor = domUtils.getNearestBlockContainer(node); while (ancestor != null && ancestor.getNodeType() == Node.ELEMENT_NODE) { String className = Element.as(ancestor).getClassName(); if (!StringUtils.isEmpty(className)) { styleNames.addAll(Arrays.asList(className.split("\\s+"))); } ancestor = ancestor.getParentNode(); } return styleNames; } @Override public boolean isExecuted() { // Always return true because we cannot prove the contrary (and it's not important). return true; } }