/**
* Copyright 2009 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package org.waveprotocol.wave.client.editor.content.paragraph;
import static org.waveprotocol.wave.client.editor.content.paragraph.ParagraphEventHandler.attributeKeptOnNewline;
import static org.waveprotocol.wave.client.editor.content.paragraph.ParagraphEventHandler.getBehaviour;
import static org.waveprotocol.wave.client.editor.content.paragraph.ParagraphEventHandler.indent;
import org.waveprotocol.wave.client.editor.NodeEventHandlerImpl;
import org.waveprotocol.wave.client.editor.content.CMutableDocument;
import org.waveprotocol.wave.client.editor.content.ContentElement;
import org.waveprotocol.wave.client.editor.content.ContentNode;
import org.waveprotocol.wave.client.editor.content.ContentPoint;
import org.waveprotocol.wave.client.editor.content.ContentTextNode;
import org.waveprotocol.wave.client.editor.content.FullContentView;
import org.waveprotocol.wave.client.editor.event.EditorEvent;
import org.waveprotocol.wave.model.document.MutableDocument;
import org.waveprotocol.wave.model.document.operation.Attributes;
import org.waveprotocol.wave.model.document.operation.impl.AttributesImpl;
import org.waveprotocol.wave.model.document.util.LineContainers;
import org.waveprotocol.wave.model.document.util.Point;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
public class LocalParagraphEventHandler extends NodeEventHandlerImpl {
boolean isEmptyLine(ContentElement e) {
// The line containing element e is considered empty if its line element is the last
// element or if it is followed by another line element
ContentElement lineElement = Line.fromParagraph(e).getLineElement();
CMutableDocument doc = lineElement.getMutableDoc();
ContentNode next = doc.getNextSibling(lineElement);
return next == null
|| (next.asElement() != null && LineRendering.isLineElement(next.asElement()));
}
@Override
public boolean handleEnter(ContentElement element, EditorEvent event) {
ContentPoint contentPoint = event.getCaret();
if (!contentPoint.isInTextNode() && contentPoint.getContainer() != element) {
return true;
}
Point<ContentNode> point = contentPoint.asPoint();
ContentNode nodeBefore = point.isInTextNode()
? point.getContainer() : Point.nodeBefore(FullContentView.INSTANCE, point.asElementPoint());
Line line = Line.fromParagraph(element);
// If user hits enter at the an empty list item - we de-indent or stop the list
if (getBehaviour(element) == ParagraphBehaviour.LIST
&& isEmptyLine(element)) {
return handleBackspaceAtBeginning(element, event);
}
Map<String, String> secondAttrs = new HashMap<String, String>();
// TODO(patcoleman): use StringMap<String> instead?
// copy whitelisted attributes
CMutableDocument doc = element.getMutableDoc();
Map<String, String> currentAttrs = line.getAttributes();
if (currentAttrs != null) {
for (Entry<String, String> entry : currentAttrs.entrySet()) {
if (attributeKeptOnNewline(entry.getKey(), entry.getValue())) {
secondAttrs.put(entry.getKey(), entry.getValue());
}
}
}
// rewrite to null if no attributes
if (secondAttrs.isEmpty()) {
secondAttrs = Attributes.EMPTY_MAP;
}
ContentElement newLineElement = doc.createElement(
doc.locate(doc.getLocation(point)), LineContainers.LINE_TAGNAME, secondAttrs);
ContentElement newLocalParagraph = Line.fromLineElement(newLineElement).getParagraph();
element.getSelectionHelper().setCaret(
Point.start(element.getRenderedContentView(), newLocalParagraph));
return true;
}
@Override
public boolean handleBackspaceAtBeginning(ContentElement paragraph, EditorEvent event) {
Line line = Line.fromParagraph(paragraph);
ContentElement lineElement = line.getLineElement();
if (line.getIndent() > 0) {
indent(lineElement, -1);
} else {
switch (line.getBehaviour()) {
case LIST:
lineElement.getMutableDoc().setElementAttribute(
lineElement, Paragraph.SUBTYPE_ATTR, null);
lineElement.getMutableDoc().setElementAttribute(
lineElement, Paragraph.LIST_STYLE_ATTR, null);
break;
default:
maybeRemove(line);
break;
}
}
return true;
}
@Override
public boolean handleDeleteAtEnd(ContentElement p, EditorEvent event) {
Line line = Line.fromParagraph(p);
if (line.next() != null) {
maybeRemove(line.next());
}
return true;
}
private void maybeRemove(Line line) {
MutableDocument<ContentNode, ContentElement, ContentTextNode> doc = line.getMutableDoc();
ContentElement lineElement = line.getLineElement();
Line previousLine = line.previous();
if (previousLine != null) {
ContentElement prevLineElement = previousLine.getLineElement();
ContentElement prevParagraph = previousLine.getParagraph();
if (doc.getNextSibling(prevLineElement) == lineElement) {
// If the previous line is empty
Map<String, String> attrs = doc.getAttributes(lineElement);
doc.setElementAttributes(prevLineElement, new AttributesImpl(attrs));
}
int at = doc.getLocation(Point.<ContentNode>end(prevParagraph));
boolean needsAdjusting = prevParagraph.getFirstChild() == null;
doc.deleteNode(lineElement);
if (!needsAdjusting) {
lineElement.getSelectionHelper().setCaret(doc.locate(at));
} else {
// NOTE(patcoleman): a special case for empty local paragraphs, these are skipped by locate
lineElement.getSelectionHelper().setCaret(
Point.<ContentNode, ContentElement>start(doc, prevParagraph));
}
}
}
}