Package org.waveprotocol.wave.client.editor.harness

Source Code of org.waveprotocol.wave.client.editor.harness.EditorHarness$EditorBundle

/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements.  See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership.  The ASF licenses this file
* to you 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.harness;

import com.google.gwt.core.client.Duration;
import com.google.gwt.core.client.GWT;
import com.google.gwt.core.client.GWT.UncaughtExceptionHandler;
import com.google.gwt.dom.client.Document;
import com.google.gwt.dom.client.Element;
import com.google.gwt.event.dom.client.ChangeEvent;
import com.google.gwt.event.dom.client.ChangeHandler;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.event.logical.shared.SelectionEvent;
import com.google.gwt.event.logical.shared.SelectionHandler;
import com.google.gwt.event.logical.shared.ValueChangeEvent;
import com.google.gwt.event.logical.shared.ValueChangeHandler;
import com.google.gwt.user.client.ui.Button;
import com.google.gwt.user.client.ui.CheckBox;
import com.google.gwt.user.client.ui.Composite;
import com.google.gwt.user.client.ui.FlowPanel;
import com.google.gwt.user.client.ui.HTML;
import com.google.gwt.user.client.ui.HorizontalPanel;
import com.google.gwt.user.client.ui.InlineLabel;
import com.google.gwt.user.client.ui.Label;
import com.google.gwt.user.client.ui.ListBox;
import com.google.gwt.user.client.ui.MultiWordSuggestOracle;
import com.google.gwt.user.client.ui.RootPanel;
import com.google.gwt.user.client.ui.SuggestBox;
import com.google.gwt.user.client.ui.SuggestOracle;
import com.google.gwt.user.client.ui.TextArea;
import com.google.gwt.user.client.ui.Widget;

import org.waveprotocol.wave.client.common.util.KeySignalListener;
import org.waveprotocol.wave.client.common.util.LogicalPanel;
import org.waveprotocol.wave.client.common.util.SignalEvent;
import org.waveprotocol.wave.client.debug.logger.DomLogger;
import org.waveprotocol.wave.client.debug.logger.LogLevel;
import org.waveprotocol.wave.client.debug.logger.LoggerListener;
import org.waveprotocol.wave.client.doodad.diff.DiffAnnotationHandler;
import org.waveprotocol.wave.client.doodad.link.LinkAnnotationHandler;
import org.waveprotocol.wave.client.doodad.link.LinkAnnotationHandler.LinkAttributeAugmenter;
import org.waveprotocol.wave.client.editor.Editor;
import org.waveprotocol.wave.client.editor.EditorImpl;
import org.waveprotocol.wave.client.editor.EditorSettings;
import org.waveprotocol.wave.client.editor.EditorStaticDeps;
import org.waveprotocol.wave.client.editor.EditorUpdateEvent;
import org.waveprotocol.wave.client.editor.EditorUpdateEvent.EditorUpdateListener;
import org.waveprotocol.wave.client.editor.Editors;
import org.waveprotocol.wave.client.editor.ElementHandlerRegistry;
import org.waveprotocol.wave.client.editor.content.ContentDocument;
import org.waveprotocol.wave.client.editor.content.ContentDocument.Level;
import org.waveprotocol.wave.client.editor.content.ContentNode;
import org.waveprotocol.wave.client.editor.content.Registries;
import org.waveprotocol.wave.client.editor.content.misc.StyleAnnotationHandler;
import org.waveprotocol.wave.client.editor.content.paragraph.LineRendering;
import org.waveprotocol.wave.client.editor.keys.KeyBindingRegistry;
import org.waveprotocol.wave.client.editor.util.EditorDocFormatter;
import org.waveprotocol.wave.client.editor.webdriver.EditorWebDriverUtil;
import org.waveprotocol.wave.client.scheduler.ScheduleCommand;
import org.waveprotocol.wave.client.scheduler.Scheduler;
import org.waveprotocol.wave.client.scheduler.Scheduler.Task;
import org.waveprotocol.wave.client.widget.popup.PopupChrome;
import org.waveprotocol.wave.client.widget.popup.PopupChromeProvider;
import org.waveprotocol.wave.client.widget.popup.simple.Popup;
import org.waveprotocol.wave.common.logging.AbstractLogger;
import org.waveprotocol.wave.common.logging.LoggerBundle;
import org.waveprotocol.wave.model.document.operation.DocInitialization;
import org.waveprotocol.wave.model.document.operation.DocOp;
import org.waveprotocol.wave.model.document.operation.automaton.DocOpAutomaton.ViolationCollector;
import org.waveprotocol.wave.model.document.operation.automaton.DocumentSchema;
import org.waveprotocol.wave.model.document.operation.impl.DocOpValidator;
import org.waveprotocol.wave.model.document.parser.XmlParseException;
import org.waveprotocol.wave.model.document.util.DocIterate;
import org.waveprotocol.wave.model.document.util.DocProviders;
import org.waveprotocol.wave.model.document.util.LineContainers;
import org.waveprotocol.wave.model.operation.OperationException;
import org.waveprotocol.wave.model.operation.SilentOperationSink;
import org.waveprotocol.wave.model.schema.conversation.ConversationSchemas;

import java.util.ArrayList;
import java.util.Map;

/**
*
*/
public class EditorHarness extends Composite implements KeySignalListener {

  private static final String TOPLEVEL_CONTAINER_TAGNAME = "body";

  static {
    LineContainers.setTopLevelContainerTagname(TOPLEVEL_CONTAINER_TAGNAME);
  }

  /**
   * Debug logger for the test module
   */
  LoggerBundle logger = new DomLogger("test");

  ContentDocument doc1, doc2;

  /**
   * The editors we are testing
   */
  Editor editor1;
  EditorBundle editorBundle1;

  /**
   * The editors we are testing
   */
  Editor editor2;
  EditorBundle editorBundle2;

  LogicalPanel.Impl displayDoc1 = new LogicalPanel.Impl() {
    {
      setElement(Document.get().createDivElement());
      getElement().getStyle().setProperty("border", "1px dashed silver");
    }
  };

  /**
   * Flag is ops coming out from editor1 should go to editor2
   */
  public boolean sendOps = true;

  /**
   * Flag if harness should operate quitly
   */
  boolean quiet = false;

  /**
   * Output widgets for editor1's content
   */
  private HTML prettyContent1;

  /**
   * Output widget for editor1's html content
   */
  private HTML prettyHtml1;

  /**
   * Output widgets for editor2's content
   */
  private HTML prettyContent2;

  /**
   * Output widget for editor2's html content
   */
  private HTML prettyHtml2;

  /**
   * Oracle for contentBox
   */
  MultiWordSuggestOracle contentOracle;

  /**
   * Input content box. Note: Only one of these regions is used.
   */
  private TextArea contentBox;
  private SuggestBox contentSuggestBox;

  /**
   * Input content box
   */
  private final Button setContentButton = new Button("Set content:", new ClickHandler() {
    public void onClick(ClickEvent e) {
      setFromContentBox();
    }
  });

  /**
   * Output widget for last operation
   */
  private final HTML operationOutput = new HTML();

  /**
   * Report errors here
   *
   * Error is triggered by error log messages.
   * Fatal is triggered by fatal log messages and uncaught exceptions.
   */
  private HTML error = null;
  private HTML fatal = null;

  /**
   * Our log div
   */
  private HTML log = null;

  private final Registries testEditorRegistries = Editor.ROOT_REGISTRIES;

  /**
   * Checkbox that toggles editing/display
   */
  private CheckBox toggleEditCheck1;

  private CheckBox toggleEditCheck2;

  private CheckBox createEditToggleCheckBox(final Editor editor) {
    CheckBox check = new CheckBox("Toggle edit");
    check.addValueChangeHandler(new ValueChangeHandler<Boolean>() {
      @Override
      public void onValueChange(ValueChangeEvent<Boolean> event) {
        setEditing(editor, event.getValue());
      }
    });
    return check;
  }

  private void setEditing(Editor editor, boolean isEditing) {
    editor.setEditing(isEditing);
    if (editor.isEditing()) {
      editor.focus(true);
    }
  }

  /**
   * Clear log button
   */
  private final Button clearLogButton = new Button("Clear log", new ClickHandler() {
    public void onClick(ClickEvent e) {
      if (LogLevel.showDebug() && log != null) {
        log.setHTML("");
        error.setText("");
        fatal.setText("");
        editor1.focus(true);
      }
    }
  });

  /**
   * Quiet button
   */
  private final Button quietButton = new Button("Quiet (app runs faster)", new ClickHandler() {
    public void onClick(ClickEvent e) {
      littleLogging();
      quiet = true;
      editor1.focus(true);
    }
  });

  /**
   * Loud button
   */
  private final Button loudButton = new Button("Loud (more info)", new ClickHandler() {
    public void onClick(ClickEvent e) {
      lottaLogging();
      quiet = false;
      editor1.focus(true);
    }
  });


  private final CheckBox createEditor2DocDetached = new CheckBox("Create detached");

  /**
   * Sets content in both editors
   * @param content
   */
  private void syncEditors(String content)  {
    DocInitialization op;
    try {
      op = DocProviders.POJO.parse(content).asOperation();
    } catch (IllegalArgumentException e) {
      if (e.getCause() instanceof XmlParseException) {
        logger.error().log("Ill-formed XML string ", e.getCause());
      } else {
        logger.error().log("Error", e);
      }
      return;
    }

    DocumentSchema schema = getSchema();

    ViolationCollector vc = new ViolationCollector();
    if (!DocOpValidator.validate(vc, schema, op).isValid()) {
      logger.error().log("That content does not conform to the schema", vc);
      return;
    }

    boolean useHack = createEditor2DocDetached.getValue();

    // EDITOR1: Pojo -> editor mode
    double start = Duration.currentTimeMillis();
    doc1 = new ContentDocument(testEditorRegistries, op, schema);
    double middle = Duration.currentTimeMillis();
    editor1.setContent(doc1);
    double end = Duration.currentTimeMillis();
    logger.log(AbstractLogger.Level.TRACE, "Set content1 took: " + (end - start)
        + " (Pojo creation: " + (middle - start) + ", rendering: " + (end - middle));

    // EDITOR2: Build document in edit mode
    start = Duration.currentTimeMillis();
    doc2 = new ContentDocument(schema);
    doc2.setRegistries(testEditorRegistries);
    editor2.setContent(doc2);

    // HACK to ensure it's created non-attached to dom, to ensure fair speed comparisons
    double start2, end2;
    if (useHack) {
      Element docDiv = doc2.getFullContentView().getDocumentElement().getImplNodelet();
      Element editorDiv = editor2.getWidget().getElement();
      assert docDiv.getParentElement() == editorDiv;
      docDiv.removeFromParent();

      start2 = Duration.currentTimeMillis();
      doc2.consume(op);
      end2 = Duration.currentTimeMillis();

      editorDiv.appendChild(docDiv);
    } else {
      start2 = Duration.currentTimeMillis();
      doc2.consume(op);
      end2 = Duration.currentTimeMillis();
    }

    end = Duration.currentTimeMillis();
    logger.log(AbstractLogger.Level.TRACE, "Set content2 took: " + (end - start) +
        " Just the op: " + (end2 - start2) + " Op + appendChild: " + (end - start2));


    documentModeSelect.setSelectedIndex(doc1.getLevel().ordinal());

    outputBothEditorStates();
  }

  /**
   * Shows a red ERROR! message above the log panel.
   */
  private void showRedErrorIndicator() {
    error.setText("ERROR!");
  }

  /**
   * Shows a red FATAL! message above the log panel.
   */
  private void showRedFatalIndicator() {
    fatal.setText("FATAL!");
  }
  /**
   * Logs an error exception and shouts error!
   *
   * @param t
   */
  private void logUncaughtExceptions(Throwable t) {
    showRedFatalIndicator();
    logger.fatal().log(t);
    logger.trace();
    GWT.log("Uncaught Exception", t);
    t.printStackTrace(System.err);
  }
//
//  /**
//   * @param testCase
//   */
//  private void runTestCase(TestBase testCase) {
//    String content = editor1.getContent();
//    boolean pass = true;
//    try {
//      sendOps = false;
//      littleLogging();
//      testCase.runTests();
//    } catch (Throwable t) {
//      GWT.getUncaughtExceptionHandler().onUncaughtException(t);
//      pass = false;
//    } finally {
//      sendOps = true;
//      lottaLogging();
//    }
//
//    if (pass) {
//      logger.trace().log("<span style='color:green'>Pass :-)</span>");
//      syncEditors(content);
//    } else {
//      syncEditors();
//    }
//    editor1.focus();
//  }
//
//  /**
//   * Operations test button
//   */
//  private final Button operationTestButton = new Button("Ops tests", new ClickHandler() {
//    public void onClick(ClickEvent e) {
//      runTestCase(new OperationTest());
//    }
//  });
//
//  /**
//   * Paragaph test button
//   */
//  private final Button pTestButton = new Button("P tests", new ClickHandler() {
//    public void onClick(ClickEvent e) {
//      runTestCase(new ParagraphTest());
//    }
//  });
//
//  /**
//   * Title test button
//   */
//  private final Button titleTestButton = new Button("Title tests", new ClickHandler() {
//    public void onClick(ClickEvent e) {
//      runTestCase(new ParagraphTest());
//    }
//  });
//
//  /**
//   * Image thumbnail test button
//   */
//  private final Button thumbnailTestButton = new Button("Thumb tests", new ClickHandler() {
//    public void onClick(ClickEvent e) {
//      runTestCase(new ImageThumbnailTest());
//    }
//  });


  /**
   * A queue of operations
   */
  ArrayList<DocOp> queue = new ArrayList<DocOp>();

  /**
   * Checkbox if harness should queue up operations from editor1 rather
   * than sending them straight to editor2
   */
  private final CheckBox queuingCheck = new CheckBox("Queue");
  private final ClickHandler queuingCheckHandler = new ClickHandler() {
    public void onClick(ClickEvent e) {
      if (!queuingCheck.getValue()) {
        // Play any remaining operations in the queue
        while (queue.size() > 0) {
          playOne();
        }
      }
      editor1.focus(true);
    }
  };

  /**
   * Button for playing queued operations
   */
  private final Button playButton = new Button("Play", new ClickHandler() {
    public void onClick(ClickEvent e) {
      playOne();
    }
  });

  /**
   * Button for clearing diff annotations and problem markers
   */
  private final Button clearAnnotationsButton = new Button("Clear Annotations",
      new ClickHandler() {
        public void onClick(ClickEvent e) {
          editorBundle1.clearDiffs();
//          ((EditorImpl)editor1).clearProblemMarkers();
        }
      }
  );

  /**
   * Checkbox if editor2 should show incoming operations as diffs
   */
  private final CheckBox diffCheck = new CheckBox("Diffs");
  private final ClickHandler diffCheckHandler = new ClickHandler() {
    public void onClick(ClickEvent e) {
      editorBundle2.setShowDiffMode(diffCheck.getValue());
    }
  };

  private final CheckBox disabledCheck = new CheckBox("Disable");
  private final ClickHandler disabledCheckHandler = new ClickHandler() {
    public void onClick(ClickEvent e) {
      ((EditorImpl) editor1).debugSetDisabled(disabledCheck.getValue());
    }
  };

  private final ListBox documentModeSelect = new ListBox();
  {
    for (Level level : Level.values()) {
      documentModeSelect.addItem(level.name());
    }
    documentModeSelect.addChangeHandler(new ChangeHandler() {
      @Override
      public void onChange(ChangeEvent event) {
        setEditorLevel();
      }
    });
  }

  private void setEditorLevel() {
    Level newLevel = Level.values()[documentModeSelect.getSelectedIndex()];
    Level current = doc1.getLevel();

    logger.log(AbstractLogger.Level.TRACE, "Switching to " + newLevel);

    if (current == newLevel) {
      return;
    }

    if (current == Level.EDITING) {
      if (newLevel == Level.SHELVED) {
        editor1.removeContentAndUnrender();
      } else {
        editor1.removeContent();
      }
      doc1.replaceOutgoingSink(editor1Sink);
    }


    if (newLevel == Level.SHELVED) {
      doc1.setShelved();
    } else if (newLevel == Level.EDITING) {
      editor1.setContent(doc1);
    } else {
      assert newLevel == Level.RENDERED || newLevel == Level.INTERACTIVE;
      if (newLevel == Level.RENDERED) {
        doc1.setRendering();
      } else {
        doc1.setInteractive(displayDoc1);
      }
    }

    assert doc1.getLevel() == newLevel;

    if (doc1.getLevel() == Level.RENDERED || doc1.getLevel() == Level.INTERACTIVE) {
      displayDoc1.getElement().appendChild(
          doc1.getFullContentView().getDocumentElement().getImplNodelet());
    } else if (doc1.getLevel() == Level.SHELVED) {
      for (ContentNode n : DocIterate.deep(
          doc1.getFullContentView(), doc1.getFullContentView().getDocumentElement(), null)) {
        assert n.getImplNodelet() == null;
      }
    }
  }

  /**
   * Clears both editors
   */
  private final Button clearEditorsButton = new Button("Clear", new ClickHandler() {
    public void onClick(ClickEvent e) {
      clearEditors();
      editor1.focus(true);
    }
  });

  private final FlowPanel widgetRow;

  public void clearEditors() {
    syncEditors("");
    LineContainers.appendLine(((EditorImpl) editor2).mutable(), null);
  }

  private int randomTestCounter;
  Scheduler.IncrementalTask randomTestProcess = new Scheduler.IncrementalTask() {
    @Override
    public boolean execute() {
      randomTestCounter++;

      logger.trace().log("Random test #" + randomTestCounter);

      try {
        // TODO(nigeltao): This might trigger an (incorrect) exception where the
        // FakeAtttachmentsManager returns null. I should fix that.
//        new ContentAnnotationPainterRandomTest().testPainter();
      } catch (RuntimeException e) {
        logger.error().log(e);
        logger.error().log("TEST FAILED");
        return true;
      }

      return true;
    }
  };

  /**
   * Plays a single operation from the queue
   */
  private void playOne() {
    if (queue.size() > 0) {
      DocOp operation = queue.remove(0);
      try {
        outputOperation(operation);
        editorBundle2.execute(operation);
        outputEditorState(editor2, prettyContent2, prettyHtml2);
      } catch (Throwable t) {
        GWT.getUncaughtExceptionHandler().onUncaughtException(t);
      }
    }
    setPlayButtonState();
  }

  /**
   * Sets enabled/disabled state of play button
   */
  private void setPlayButtonState() {
    playButton.setEnabled(queue.size() > 0);
    playButton.setHTML("Play" +
        (queue.size() > 0 ? " (" + queue.size() + ")" : ""));
  }

  /**
   * Output content
   */
  private void outputBothEditorStates() {
    outputEditorState(editor1, prettyContent1, prettyHtml1);
    outputEditorState(editor2, prettyContent2, prettyHtml2);
  }

  /**
   * Output content for and editor
   */
  private void outputEditorState(final Editor editor,
      final HTML prettyContent, final HTML prettyHtml) {
    Runnable printer = new Runnable() {
      public void run() {
        if (!quiet) {
          String content = EditorDocFormatter.formatContentDomString(editor);
          String html = EditorDocFormatter.formatImplDomString(editor);
          if (content != null) {
            prettyContent.setText(content);
          }
          if (html != null) {
            prettyHtml.setText(html);
          }
        }
      }
    };

    if (LogLevel.showDebug()) {
      // Flush and update once it's safe
      if (editor.getContent().flush(printer)) {
        printer.run(); // note that if true is returned, the command isn't run inside flush.
      }
    }
  }

  /**
   * Output an operation
   *
   * @param operation
   */
  private void outputOperation(DocOp operation) {
    if (!quiet) {
      operationOutput.setText(operation != null ? operation.toString() : "");
    }
  }

  /**
   * Sets content in both editors from current text in content box.
   * Also adds that content to the content box oracle.
   */
  private void setFromContentBox() {
    String content = contentBox != null ? contentBox.getText() : contentSuggestBox.getText();
    if (contentOracle != null) {
      contentOracle.add(content);
    }
    syncEditors(bodyWrap(content));
    editor1.focus(true);
  }

  /**
   * This is used for unit testing to call this to attach the main panel to an
   * arbitrary root. The unit testing framework does not call onModuleLoad().
   *
   */
  public static EditorHarness createTestPage() {
    EditorHarness harness = new EditorHarness();
    RootPanel.get().add(harness, 0, 0);
    return harness;
  }

  void initContentText() {
    contentBox = new TextArea();
    contentBox.getElement().setId("content-box");
  }

  void initContentOracle() {
    contentOracle = new MultiWordSuggestOracle();

    contentSuggestBox = new SuggestBox(contentOracle);
    contentSuggestBox.getElement().setId("content-box");

    // Some initial content xml strings
    contentOracle.add("");
    contentOracle.add("abcd");

    contentSuggestBox.addSelectionHandler(new SelectionHandler<SuggestOracle.Suggestion>() {
      @Override public void onSelection(SelectionEvent<SuggestOracle.Suggestion> event) {
        setFromContentBox();
      }
    });

    String[] extra = extendSampleContent();
    if (extra != null) {
      for (String content : extra) {
        contentOracle.add(content);
      }
    }
  }

  /**
   * Override this to provide additional sample content for the suggest box
   */
  public String[] extendSampleContent() {
    return null;
  }

  /**
   * Convenience method to wrap sample content in the required body and line tags.
   */
  public String bodyWrap(String sampleContent) {
    // Put an extra space so that the content oracle indexing works nicely on
    // the actual sample content.
    return "<body><line/> " + sampleContent + "</body>";
  }

  public EditorHarness() {
    try {
      // Start with queueing off
      queuingCheck.setValue(false);
      setPlayButtonState();

      // Construct log panel
      if (LogLevel.showDebug()) {
        log = new HTML();
        log.setStyleName("log");
        log.getElement().setId("log1");
        error = new HTML();
        error.getElement().setId("error1");
        error.setStyleName("redIndicator");
        fatal = new HTML();
        fatal.getElement().setId("fatal1");
        fatal.setStyleName("redIndicator");

        // Initialize listener that turns error indicator red when any error
        // messages are logged
        DomLogger.addLoggerListener(new LoggerListener() {
          @Override
          public void onError() {
            showRedErrorIndicator();
          }

          @Override
          public void onFatal() {
            showRedFatalIndicator();
          }

          @Override
          public void onNeedOutput() {}

          @Override
          public void onNewLogger(String loggerName) {}
        });
      }

      // Setup content and html display
      if (LogLevel.showDebug()) {
        prettyContent1 = new HTML();
        prettyContent1.addStyleName("content");
        prettyContent2 = new HTML();
        prettyContent2.addStyleName("content");
        prettyHtml1 = new HTML();
        prettyHtml1.addStyleName("html");
        prettyHtml2 = new HTML();
        prettyHtml2.addStyleName("html");
      }

      EditorWebDriverUtil.setDocumentSchema(getSchema());

      registerDoodads(testEditorRegistries);

      // The editors we are testing
      editor1 = createEditor("editor1");
      editorBundle1 = new EditorBundle(editor1, true);
      editor2 = createEditor("editor2");
      editorBundle2 = new EditorBundle(editor2, false);
      toggleEditCheck1 = createEditToggleCheckBox(editor1);
      toggleEditCheck2 = createEditToggleCheckBox(editor2);

      operationOutput.setStyleName("operation");

      final FlowPanel editMain = new FlowPanel();
      editMain.setStyleName("main");
      HorizontalPanel editors = new HorizontalPanel();
      FlowPanel editorStack1 = new FlowPanel();
      FlowPanel editorStack2 = new FlowPanel();

      editorStack1.add(editor1.getWidget());
      editorStack1.add(displayDoc1);
      editorStack1.add(toggleEditCheck1);
      editorStack1.add(disabledCheck);
      editorStack1.add(documentModeSelect);
//      xx editorStack1.add(disabledCheck);
      if (LogLevel.showDebug()) {
        editorStack1.add(prettyContent1);
      }

      editorStack2.add(editor2.getWidget());
      editorStack2.add(toggleEditCheck2);
      editorStack2.add(diffCheck);
      editorStack2.add(createEditor2DocDetached);
      if (LogLevel.showDebug()) {
        editorStack2.add(prettyContent2);
      }

      editors.add(editorStack1);
      if (LogLevel.showDebug()) {
        editors.add(prettyHtml1);
      }
      editors.add(editorStack2);
      if (LogLevel.showDebug()) {
        editors.add(prettyHtml2);
      }

      HorizontalPanel operations = new HorizontalPanel();
      operations.setVerticalAlignment(HorizontalPanel.ALIGN_MIDDLE);
      queuingCheck.addClickHandler(queuingCheckHandler);
      disabledCheck.addClickHandler(disabledCheckHandler);
      diffCheck.addClickHandler(diffCheckHandler);
      setPlayButtonState();
      operations.add(clearAnnotationsButton);
      operations.add(queuingCheck);
      operations.add(playButton);
      operations.add(new Label("Operation:"));
      operations.add(operationOutput);

      editMain.add(operations);
      editMain.add(editors);

      // Exactly one of these methods should be uncommented.
//      initContentText();
      initContentOracle();

      widgetRow = new FlowPanel();

      widgetRow.add(clearEditorsButton);
      widgetRow.add(setContentButton);

      widgetRow.add(new InlineLabel("<body><line/>"));
      widgetRow.add(contentBox != null ? contentBox : contentSuggestBox);
      widgetRow.add(new InlineLabel("</body>"));

      Button clearContentBoxButton = new Button("Clear text", new ClickHandler() {
        public void onClick(ClickEvent e) {
          if (contentBox != null) {
            contentBox.setValue("");
          } else {
            contentSuggestBox.setValue("");
          }
        }
      });
      clearContentBoxButton.getElement().setId("clear-content-box");
      widgetRow.add(clearContentBoxButton);
      // Hide it, only really used for webdriver tests
      clearContentBoxButton.setVisible(false);

      if (LogLevel.showDebug()) {
        widgetRow.add(new FlowPanel());
        widgetRow.add(clearLogButton);
        widgetRow.add(quietButton);
        widgetRow.add(loudButton);
      }

      editMain.add(widgetRow);

      if (LogLevel.showDebug()) {
        editMain.add(error);
        editMain.add(fatal);
        editMain.add(log);
        // We need our own uncaught exception hander to make sure the error shout happens
        GWT.setUncaughtExceptionHandler(new UncaughtExceptionHandler() {
          public void onUncaughtException(Throwable e) {
            logUncaughtExceptions(e);
          }
        });
      } else {
        HTML spacer = new HTML();
        editMain.add(spacer);
      }

      initWidget(editMain);

      // Enable logging (important to do this after main is attached
      // such that exceptions occurring before this can be handled by
      // GWT's Default handler...)
      if (LogLevel.showDebug()) {
        DomLogger.enable(log.getElement());
        lottaLogging();
      }

      KeyBindingRegistry keysRegistry = new KeyBindingRegistry();
      extendKeyBindings(keysRegistry);

      // Start editing
      editor1.init(testEditorRegistries, keysRegistry, EditorSettings.DEFAULT);
      editor2.init(testEditorRegistries, keysRegistry, EditorSettings.DEFAULT);
      editor1.addUpdateListener(new EditorUpdateListener() {
        @Override
        public void onUpdate(EditorUpdateEvent event) {
          outputBothEditorStates();
        }

      });

      editor1.setOutputSink(editor1Sink);
      editor2.setOutputSink(editor2Sink);
      clearEditors();
      editor1.setEditing(true);
      editor2.setEditing(false);
      toggleEditCheck2.setValue(false);
      toggleEditCheck1.setValue(true);

      // Output initial state
      outputBothEditorStates();
      outputOperation(null);

    } catch (RuntimeException r) {
      // Do we need this at all?

      UncaughtExceptionHandler handler = GWT.getUncaughtExceptionHandler();
      if (handler != null) {
        handler.onUncaughtException(r);
      } else {
        logger.error().log(r);
      }
      throw r;
    }
  }

  public DocumentSchema getSchema() {
    return ConversationSchemas.BLIP_SCHEMA_CONSTRAINTS;
  }

  public void registerDoodads(Registries registries) {

    ElementHandlerRegistry testHandlerRegistry =
        testEditorRegistries.getElementHandlerRegistry();

    LineRendering.registerContainer(TOPLEVEL_CONTAINER_TAGNAME,
        registries.getElementHandlerRegistry());

    StyleAnnotationHandler.register(registries);
    DiffAnnotationHandler.register(
        registries.getAnnotationHandlerRegistry(),
        registries.getPaintRegistry());

    LinkAnnotationHandler.register(registries, new LinkAttributeAugmenter() {
      @Override
      public Map<String, String> augment(Map<String, Object> annotations, boolean isEditing,
          Map<String, String> current) {
        return current;
      }
    });

    // TODO(danilatos): Open source spelly stuff
//    SpellDocument testSpellDocument = SpellDebugHelper.createTestSpellDocument(
//        EditorStaticDeps.logger);
//    SpellAnnotationHandler.register(Editor.ROOT_REGISTRIES,
//        SpellySettings.DEFAULT, testSpellDocument);
//    SpellDebugHelper.setDebugSpellDoc(testSpellDocument);
//    SpellSuggestion.register(testHandlerRegistry, testSpellDocument);
//    SpellTesting.registerDebugCombo(keysRegistry);
//    SpellDebugHelper.setDebugSpellDoc(testSpellDocument);

    extend(registries);
  }

  /** Override this method to register additional doodads. */
  public void extend(Registries registries) {
  }

  /** Override this method to add keyboard handling hooks. */
  public void extendKeyBindings(KeyBindingRegistry registry) {
  }

  static {
    Editors.initRootRegistries();
  }

  private class EditorBundle {
    private final HighlightingDiffState diffState;
    private final boolean is1;
    private boolean showDiffs;

    EditorBundle(Editor editor, boolean is1) {
      this.diffState = new HighlightingDiffState(editor);
      this.is1 = is1;
    }

    void setShowDiffMode(boolean showDiffs) {
      this.showDiffs = showDiffs;
    }

    void execute(DocOp op) throws OperationException {
      if (showDiffs) {
        diffState.consume(op);
      } else {
        // TODO(danilatos): Clean this up
        (is1 ? doc1 : doc2).consume(op);
      }
    }

    void clearDiffs() {
      diffState.clearDiffs();
    }
  }

  private Editor createEditor(String id) {
    EditorStaticDeps.setPopupProvider(Popup.LIGHTWEIGHT_POPUP_PROVIDER);
    EditorStaticDeps.setPopupChromeProvider(new PopupChromeProvider() {
      public PopupChrome createPopupChrome() {
        return null;
      }
    });

    Editor editor = Editors.create();
    editor.getWidget().getElement().setId(id);
    editor.addKeySignalListener(this);
    return editor;
  }

  /**
   * Log from all relevant modules
   */
  private void lottaLogging() {
    DomLogger.enableAllModules();
    DomLogger.enableModule("test", true);
    DomLogger.enableModule("editor", true);
    DomLogger.enableModule("editor-node", true);
    DomLogger.enableModule("operator", true);
    DomLogger.enableModule("dragdrop", true);
  }

  /**
   * Only log from test module
   */
  private void littleLogging() {
    DomLogger.setMaxLevel(AbstractLogger.Level.ERROR);
    DomLogger.enableModule("test", true);
  }

  private final SilentOperationSink<DocOp> editor1Sink =
      new SilentOperationSink<DocOp>() {
        public void consume(DocOp operation) {
          try {
            if (operation != null && sendOps) {
              if (queuingCheck.getValue()) {
                queue.add(operation);
                setPlayButtonState();
              } else {
                editorBundle2.execute(operation);
              }
            }
          } catch (Throwable t) {
            GWT.getUncaughtExceptionHandler().onUncaughtException(t);
          } finally {
            outputOperation(operation);
          }

        }
  };

  private final SilentOperationSink<DocOp> editor2Sink =
      new SilentOperationSink<DocOp>() {
        public void consume(DocOp operation) {
          try {
            if (operation != null && sendOps) {
              editorBundle1.execute(operation);
            }
          } catch (Throwable t) {
            GWT.getUncaughtExceptionHandler().onUncaughtException(t);
          } finally {
            outputOperation(operation);
          }

        }
  };

  /**
   * {@inheritDoc}
   */
  public boolean onKeySignal(final Widget sender, SignalEvent event) {
    // Deferred command so we have a look at the content after it's updated
    ScheduleCommand.addCommand(new Task() {
      public void execute() {
         if (sender == editor1) {
           outputEditorState(editor1, prettyContent1, prettyHtml1);
         } else {
           outputEditorState(editor2, prettyContent2, prettyHtml2);
         }
      }
    });
    return false;
  }

  public Editor getEditor1() {
    return editor1;
  }

  public Editor getEditor2() {
    return editor2;
  }

  /**
   * Convenience method to run the harness when it's the main application.
   */
  public void run() {
    RootPanel.get().add(this);
    getEditor1().focus(true);
  }
}
TOP

Related Classes of org.waveprotocol.wave.client.editor.harness.EditorHarness$EditorBundle

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.