/** * Copyright 2008 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.debug; import com.google.gwt.dom.client.DivElement; import com.google.gwt.dom.client.Document; import com.google.gwt.dom.client.Element; import com.google.gwt.dom.client.Style.Overflow; import com.google.gwt.dom.client.Style.Unit; import com.google.gwt.event.dom.client.ClickEvent; import com.google.gwt.event.dom.client.ClickHandler; import com.google.gwt.user.client.ui.Composite; import com.google.gwt.user.client.ui.FlowPanel; import com.google.gwt.user.client.ui.RadioButton; import com.google.gwt.user.client.ui.TextArea; import org.waveprotocol.wave.client.debug.logger.DomLogger; import org.waveprotocol.wave.client.editor.EditorImpl; import org.waveprotocol.wave.client.editor.EditorUpdateEvent; import org.waveprotocol.wave.client.editor.EditorUpdateEvent.EditorUpdateListener; import org.waveprotocol.wave.client.editor.content.CMutableDocument; import org.waveprotocol.wave.client.editor.util.EditorDocFormatter; import org.waveprotocol.wave.common.logging.InMemoryLogSink; import org.waveprotocol.wave.common.logging.LogSink; import org.waveprotocol.wave.common.logging.LogUtils; import org.waveprotocol.wave.common.logging.AbstractLogger.Level; import org.waveprotocol.wave.model.document.RangedAnnotation; import java.util.List; /** * Debug pop up for editor. * */ final class DebugDialog extends Composite { /** * Text area for display the impl dom. */ private final TextArea dom = new TextArea(); /** * Text area for displaying the local content xml */ private final TextArea localXml = new TextArea(); /** * Text area for displaying the persistent document */ private final TextArea persistenDocumentContent = new TextArea(); /** * Text area for the annotations */ private final TextArea annotationContent = new TextArea(); private final EditorImpl editorImpl; private final FlowPanel mainPanel = new FlowPanel(); private final DebugOptions debugOptions; /** * Enum for the things that can be selected. */ private enum Selection { LOCAL_XML("localXml"), DOM("dom"), PERSISTENT_DOCUMENT("persistentDocument"), ANNOTATIONS("annotations"), LOG("log"), OPTIONS("options"); /** Display name of this enum value */ private final String displayName; /** Constructor */ Selection(String displayName){ this.displayName = displayName; } @Override public String toString(){ return displayName; } } /** * Tracks the content that was previously selected. */ private Selection previousSelection; /** * Radio button group for selecting which panel to display (content/editor log) */ private static int radioId = 0; private final String groupName = "__debug_dialog_selector_" + radioId++; // TODO(user): Pull radio button group into its own class. private final RadioButton domButton = new RadioButton(groupName, Selection.DOM.toString()); private final RadioButton localXmlButton = new RadioButton(groupName, Selection.LOCAL_XML.toString()); private final RadioButton persistenDocumentButton = new RadioButton(groupName, Selection.PERSISTENT_DOCUMENT.toString()); private final RadioButton annotationButton = new RadioButton(groupName, Selection.ANNOTATIONS.toString()); private final RadioButton logButton = new RadioButton(groupName, Selection.LOG.toString()); private final RadioButton optionsButton = new RadioButton(groupName, Selection.OPTIONS.toString()); private void setChecked(RadioButton button) { button.setValue(true); if (button == domButton) { setNewSelection(Selection.DOM); } else if (button == localXmlButton) { setNewSelection(Selection.LOCAL_XML); } else if (button == persistenDocumentButton){ setNewSelection(Selection.PERSISTENT_DOCUMENT); } else if (button == annotationButton){ setNewSelection(Selection.ANNOTATIONS); } else if (button == logButton){ setNewSelection(Selection.LOG); } else if (button == optionsButton) { setNewSelection(Selection.OPTIONS); } } private final ClickHandler radioButtonHandler = new ClickHandler() { public void onClick(ClickEvent e) { DebugDialog.this.setChecked((RadioButton) e.getSource()); } }; private static class LogPanel { private final InMemoryLogSink log; private final DivElement logContainer = Document.get().createDivElement(); private DivElement lastElement; LogPanel(InMemoryLogSink log) { this.log = log; logContainer.getStyle().setWidth(800, Unit.PX); logContainer.getStyle().setHeight(500, Unit.PX); logContainer.getStyle().setOverflow(Overflow.SCROLL); } public void attachTo(Element el) { el.appendChild(logContainer); } public void detach() { logContainer.removeFromParent(); } private void appendLogLine(String line) { DivElement element = Document.get().createDivElement(); element.setInnerHTML(line); element.getStyle().setProperty("borderBottomStyle", "dotted"); element.getStyle().setBorderWidth(1, Unit.PX); element.getStyle().setBorderColor("#c0c0c0"); logContainer.appendChild(element); lastElement = element; } public void fillContent() { logContainer.setInnerHTML(null); List<String> strs = log.showAll(); for (String s : strs) { appendLogLine(s); } // scroll to the bottom if there's anything to see: if (lastElement != null) { lastElement.scrollIntoView(); } } private final LogSink domSink = new LogSink() { @Override public void log(Level level, String msg) { appendLogLine(msg); lastElement.scrollIntoView(); } @Override public void lazyLog(Level level, Object... messages) { appendLogLine(LogUtils.stringifyLogObject(messages)); lastElement.scrollIntoView(); } }; /** * If true, keeps the log panel up to date with events from the logger. If * false, stop listening to updates. * * @param enable */ public void showUpdates(boolean enable) { if (enable) { fillContent(); log.addLogSink_DO_NOT_USE(domSink); } else { log.removeLogSink(domSink); } } } private final LogPanel logPanel = new LogPanel(DomLogger.logbuffer); /** * Initialisation code for the TextAreas * * @param area text area to initialise */ private void initTextArea(TextArea area) { area.setReadOnly(true); area.setVisibleLines(40); area.setCharacterWidth(80); } /** * @param editorImpl */ public DebugDialog(EditorImpl editorImpl) { // TODO(user): move inline styles to css + use declarative ui this.editorImpl = editorImpl; debugOptions = new DebugOptions(editorImpl); initWidget(mainPanel); mainPanel.add(domButton); mainPanel.add(localXmlButton); mainPanel.add(persistenDocumentButton); mainPanel.add(annotationButton); mainPanel.add(logButton); mainPanel.add(optionsButton); mainPanel.getElement().insertBefore(Document.get().createBRElement(), null); previousSelection = Selection.PERSISTENT_DOCUMENT; domButton.addClickHandler(radioButtonHandler); localXmlButton.addClickHandler(radioButtonHandler); persistenDocumentButton.addClickHandler(radioButtonHandler); annotationButton.addClickHandler(radioButtonHandler); logButton.addClickHandler(radioButtonHandler); optionsButton.addClickHandler(radioButtonHandler); initTextArea(dom); initTextArea(localXml); initTextArea(persistenDocumentContent); initTextArea(annotationContent); // Start with persistent selected. setChecked(persistenDocumentButton); } /** * Builds a string representation of the annotations in the current document. * * @return formatted string of all the annotations in the document */ private String getAnnotations() { CMutableDocument doc = editorImpl.mutable(); int end = doc.size(); // Grab a cursor over the whole document for our known keys Iterable<RangedAnnotation<Object>> rangedAnnotations = editorImpl.getContent().getLocalAnnotations().rangedAnnotations(0, end, null); StringBuilder retval = new StringBuilder(); for (RangedAnnotation<Object> ann : rangedAnnotations) { if (ann.value() != null) { retval.append("(" + ann.start() + "," + ann.end() + ") : " + ann.key() + "=" + ann.value() + "\n"); } } return retval.toString(); } EditorUpdateEvent.EditorUpdateListener updateListener = new EditorUpdateListener() { @Override public void onUpdate(EditorUpdateEvent dummy) { switch (previousSelection) { case DOM: dom.setText(EditorDocFormatter.formatImplDomString(editorImpl)); break; case LOCAL_XML: localXml.setText(EditorDocFormatter.formatContentDomString(editorImpl)); break; case PERSISTENT_DOCUMENT: persistenDocumentContent.setText( EditorDocFormatter.formatPersistentDomString(editorImpl)); break; case ANNOTATIONS: annotationContent.setText(getAnnotations()); break; case LOG: break; } } }; /** * onShow, update to show the editor's content. */ public void onShow() { switch (previousSelection) { case DOM: case LOCAL_XML: case PERSISTENT_DOCUMENT: case ANNOTATIONS: editorImpl.addUpdateListener(updateListener); updateListener.onUpdate(null); logPanel.showUpdates(false); break; case LOG: logPanel.showUpdates(true); editorImpl.removeUpdateListener(updateListener); break; } } /** * Removes the previously selected content, and displays the new content. * * @param selection what to display now */ private void setNewSelection(Selection selection) { switch (previousSelection) { case DOM: mainPanel.remove(dom); break; case LOCAL_XML: mainPanel.remove(localXml); break; case PERSISTENT_DOCUMENT: mainPanel.remove(persistenDocumentContent); break; case ANNOTATIONS: mainPanel.remove(annotationContent); break; case LOG: logPanel.detach(); break; case OPTIONS: mainPanel.remove(debugOptions.getWidget()); break; } switch (selection) { case DOM: mainPanel.add(dom); break; case LOCAL_XML: mainPanel.add(localXml); break; case PERSISTENT_DOCUMENT: mainPanel.add(persistenDocumentContent); break; case ANNOTATIONS: mainPanel.add(annotationContent); break; case LOG: logPanel.attachTo(mainPanel.getElement()); break; case OPTIONS: debugOptions.refresh(); mainPanel.add(debugOptions.getWidget()); break; } previousSelection = selection; onShow(); } public void onHide() { editorImpl.removeUpdateListener(updateListener); logPanel.showUpdates(false); } }