/**
* Copyright 2010 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.wavepanel.view.dom;
import static org.waveprotocol.wave.client.uibuilder.BuilderHelper.KIND_ATTRIBUTE;
import static org.waveprotocol.wave.client.wavepanel.view.dom.DomViewHelper.load;
import com.google.gwt.dom.client.Document;
import com.google.gwt.dom.client.Element;
import com.google.gwt.dom.client.ImageElement;
import org.waveprotocol.wave.client.common.safehtml.SafeHtmlBuilder;
import org.waveprotocol.wave.client.common.util.StringSequence;
import org.waveprotocol.wave.client.uibuilder.UiBuilder;
import org.waveprotocol.wave.client.wavepanel.view.IntrinsicBlipMetaView;
import org.waveprotocol.wave.client.wavepanel.view.View.Type;
import org.waveprotocol.wave.client.wavepanel.view.dom.full.BlipMetaViewBuilder;
import org.waveprotocol.wave.client.wavepanel.view.dom.full.BlipMetaViewBuilder.Components;
import org.waveprotocol.wave.client.wavepanel.view.dom.full.BlipViewBuilder;
import org.waveprotocol.wave.client.wavepanel.view.dom.full.TypeCodes;
import org.waveprotocol.wave.client.wavepanel.view.dom.full.WavePanelResourceLoader;
import org.waveprotocol.wave.model.util.Pair;
import java.util.EnumMap;
import java.util.EnumSet;
import java.util.Set;
/**
* BlipViewDomImpl of the blip view.
*
*/
public final class BlipMetaDomImpl implements DomView, IntrinsicBlipMetaView {
/**
* The INLINE_LOCATOR_ATTRIBUTE is the dom element attribute that contains the
* serialized string of the inline locator.
* The INLINE_LOCATOR_PROPERTY is a the dom element property that contains the deserialized
* inline locator object.
* The two must be different since in IE, property and attribute are not namespaced separately.
*/
private static final String INLINE_LOCATOR_PROPERTY = "inlineSequence";
public static final String INLINE_LOCATOR_ATTRIBUTE = "inline";
private final static BlipViewBuilder.Css CSS = WavePanelResourceLoader.getBlip().css();
/** The DOM element of this view. */
private final Element self;
/** The HTML id of {@code self}. */
private final String id;
//
// UI fields for both intrinsic and structural elements.
// Element references are loaded lazily and cached.
//
private Element time;
private Element contentContainer;
private ImageElement avatar;
private Element metaline;
private Element metabar;
private Element menu;
private StringSequence inlineLocators;
BlipMetaDomImpl(Element self, String id) {
this.self = self;
this.id = id;
}
public static BlipMetaDomImpl of(Element e) {
return new BlipMetaDomImpl(e, e.getId());
}
@Override
public void setTime(String time) {
getTime().setInnerText(time);
}
@Override
public void setAvatar(String avatarUrl) {
getAvatar().setSrc(avatarUrl);
}
@Override
public void setMetaline(String metaline) {
getMetaline().setInnerText(metaline);
}
@Override
public void setRead(boolean read) {
// The entire set of class names is always replaced, because
// server-generated and client-generated classes do not mix.
if (read) {
getMetabar().setClassName(CSS.metabar() + " " + CSS.read());
} else {
getMetabar().setClassName(CSS.metabar() + " " + CSS.unread());
}
}
@Override
public void enable(Set<MenuOption> toEnable) {
Pair<EnumMap<MenuOption, BlipMenuItemDomImpl>, EnumSet<MenuOption>> state = getMenuState();
EnumSet<MenuOption> options = EnumSet.copyOf(state.first.keySet());
EnumSet<MenuOption> selected = state.second;
options.addAll(toEnable);
setMenuState(options, selected);
}
@Override
public void disable(Set<MenuOption> toDisable) {
Pair<EnumMap<MenuOption, BlipMenuItemDomImpl>, EnumSet<MenuOption>> state = getMenuState();
EnumSet<MenuOption> options = EnumSet.copyOf(state.first.keySet());
EnumSet<MenuOption> selected = state.second;
options.removeAll(toDisable);
selected.removeAll(toDisable);
setMenuState(options, selected);
}
@Override
public void select(MenuOption option) {
BlipMenuItemDomImpl item = getMenuState().first.get(option);
if (item != null) {
item.select();
}
}
@Override
public void deselect(MenuOption option) {
BlipMenuItemDomImpl item = getMenuState().first.get(option);
if (item != null) {
item.deselect();
}
}
/**
* Scrapes the menu state from the DOM. The menu state describes what options
* exist, and which, if any, are currently selected.
*
* @return a mapping from available options to their UI objects, and the
* subset of those options that are currently selected.
*/
private Pair<EnumMap<MenuOption, BlipMenuItemDomImpl>, EnumSet<MenuOption>> getMenuState() {
EnumMap<MenuOption, BlipMenuItemDomImpl> options =
new EnumMap<MenuOption, BlipMenuItemDomImpl>(MenuOption.class);
EnumSet<MenuOption> selected = EnumSet.noneOf(MenuOption.class);
Element e = getMenu().getFirstChildElement();
while (e != null) {
if (e.hasAttribute(KIND_ATTRIBUTE)
&& e.getAttribute(KIND_ATTRIBUTE).equals(TypeCodes.kind(Type.MENU_ITEM))) {
BlipMenuItemDomImpl item = BlipMenuItemDomImpl.of(e);
MenuOption option = item.getOption();
options.put(option, item);
if (item.isSelected()) {
selected.add(option);
}
}
e = e.getNextSiblingElement();
}
return Pair.of(options, selected);
}
/**
* Replaces the current menu DOM with a new menu.
*
* @param options options to include in the menu
* @param selected which options, if any, are to be selected.
*/
private void setMenuState(Set<MenuOption> options, Set<MenuOption> selected) {
UiBuilder builder = BlipMetaViewBuilder.menuBuilder(options, selected, CSS);
SafeHtmlBuilder out = new SafeHtmlBuilder();
builder.outputHtml(out);
getMenu().setInnerHTML(out.toSafeHtml().asString());
}
public void clearContent() {
getInlineLocators().clear();
getContentContainer().getFirstChildElement().setInnerHTML("");
}
public void setContent(Element document) {
// Server-side document rendering is not correct - it leaves off the crucial
// "document" class.
document.addClassName("document");
getContentContainer().getFirstChildElement().appendChild(document);
}
public StringSequence getInlineLocators() {
if (inlineLocators == null) {
Element content = getContentContainer().getFirstChildElement();
if (content != null) {
inlineLocators = (StringSequence) content.getPropertyObject(INLINE_LOCATOR_PROPERTY);
if (inlineLocators == null) {
// Note: getAttribute() of a missing attribute does not return null on
// all browsers.
if (content.hasAttribute(INLINE_LOCATOR_ATTRIBUTE)) {
String serial = content.getAttribute(INLINE_LOCATOR_ATTRIBUTE);
inlineLocators = StringSequence.create(serial);
} else {
inlineLocators = StringSequence.create();
}
content.setPropertyObject(INLINE_LOCATOR_PROPERTY, inlineLocators);
}
} else {
// Leave inlineLocators as null, since the document is not here yet.
}
}
return inlineLocators;
}
//
// Generated code. There is no informative content in the code below.
//
private Element getTime() {
if (time == null) {
time = load(id, Components.TIME);
}
return time;
}
private ImageElement getAvatar() {
if (avatar == null) {
avatar = load(id, Components.AVATAR).cast();
}
return avatar;
}
private Element getMetaline() {
if (metaline == null) {
metaline = load(id, Components.METALINE);
}
return metaline;
}
private Element getMetabar() {
if (metabar == null) {
metabar = load(id, Components.METABAR);
}
return metabar;
}
private Element getMenu() {
if (menu == null) {
menu = load(id, Components.MENU);
}
return menu;
}
//
// Structural elements are public, in order to export structural control.
//
public Element getContentContainer() {
if (contentContainer == null) {
contentContainer = load(id, Components.CONTENT);
}
return contentContainer;
}
public void remove() {
getElement().removeFromParent();
}
//
// DomView nature.
//
@Override
public Element getElement() {
return self;
}
@Override
public String getId() {
return id;
}
//
// DOM-specific structural knowledge.
//
Element getInlineAnchorAfter(Element ref) {
String id = ref != null ? ref.getId() : null;
String nextId = getInlineLocators().getNext(id);
return Document.get().getElementById(nextId);
}
Element getInlineAnchorBefore(Element ref) {
String id = ref != null ? ref.getId() : null;
String nextId = getInlineLocators().getPrevious(id);
return Document.get().getElementById(nextId);
}
void insertInlineLocatorBefore(Element ref, Element x) {
String id = ref != null ? ref.getId() : null;
getInlineLocators().insertBefore(id, x.getId());
}
void removeInlineLocator(Element x) {
getInlineLocators().remove(x.getId());
}
@Override
public boolean equals(Object obj) {
return DomViewHelper.equals(this, obj);
}
@Override
public int hashCode() {
return DomViewHelper.hashCode(this);
}
}