Package org.waveprotocol.wave.client.wavepanel.event

Source Code of org.waveprotocol.wave.client.wavepanel.event.FocusManager

/**
* 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.wavepanel.event;

import com.google.common.base.Preconditions;
import com.google.gwt.dom.client.Document;
import com.google.gwt.dom.client.Element;
import com.google.gwt.dom.client.NativeEvent;
import com.google.gwt.event.dom.client.KeyCodes;
import com.google.gwt.event.dom.client.KeyDownEvent;
import com.google.gwt.event.dom.client.KeyDownHandler;
import com.google.gwt.event.dom.client.KeyEvent;
import com.google.gwt.event.dom.client.KeyPressEvent;
import com.google.gwt.event.dom.client.KeyPressHandler;
import com.google.gwt.event.dom.client.KeyUpEvent;
import com.google.gwt.event.dom.client.KeyUpHandler;
import com.google.gwt.user.client.Event;
import com.google.gwt.user.client.ui.ComplexPanel;
import com.google.gwt.user.client.ui.RootPanel;
import org.waveprotocol.box.stat.ExecutionTree;
import org.waveprotocol.box.stat.Timer;
import org.waveprotocol.box.stat.Timing;
import org.waveprotocol.box.webclient.stat.dialog.StatDialog;

import org.waveprotocol.wave.client.common.util.EventWrapper;
import org.waveprotocol.wave.client.common.util.KeyCombo;
import org.waveprotocol.wave.client.common.util.LinkedSequence;
import org.waveprotocol.wave.client.common.util.SignalEvent;
import org.waveprotocol.wave.client.common.util.SignalEventImpl;
import org.waveprotocol.wave.model.util.ValueUtils;

/**
* Manages keyboard focus among a set of sibling focusables, firing focus and
* blur events. Managers can be organized into a hierarchy, for modular UI
* focus.
* <p>
* From the focusables in its domain, a focus manager keeps at most one of them
* selected. Key events to this manager are routed to the selected focusable. As
* this focus manager gains and loses focus, it fires focus and blur events on
* the selected focusable.
* <p>
* To help explain the need for a hierarchy (rather than a flat domain of
* focusables), and the difference between selected and focused, consider an
* application with a windowed UI, with a main window and a chat window. In the
* main window are multiple tabs, including an explorer tab with a tree control
* of resources, and an editor tab for an open resource. If focus is in the chat
* window, and some signal occurs to place application focus on a resource in
* the tree control (e.g., a click event occurs on it), then that resource
* demands application focus (using {@link #ensureGlobalFocus()}. This causes it
* to become the selected resource in the domain of the tree control, the
* explorer tab becomes the selected tab in the domain of tabs, and the main
* window becomes the selected window in the domain of windows (stealing
* selection and focus from the chat window). Additionally, each of those
* objects becomes focused, which means that key events will be routed to them
* (top down). If application focus is moved back to the chat window, the
* explorer tab remains the selected tab in its domain, and the clicked-on
* resource remains the selected resource in its domain. The selected window
* transitions from the main window to the chat window.
* <p>
* The tree nature of focus managers is to enable modular UIs. The distinction
* between selected and focused is so that selection can remain persistent
* despite application focus moving around. The only constraint between
* selection and focus is that, for the deepest focusable with application
* focus, all its ancestors (including itself) are both focused and selected.
*
*/
public final class FocusManager implements Focusable, KeySignalHandler {

  /** Unique top-level focus manager. */
  private final static FocusManager ROOT = new FocusManager(null, true);

  public static void init() {
    DocumentPanel.install(ROOT);
  }

  /**
   * Containing focus manager if there is one. Only {@link #ROOT} is intended to
   * be parent-less.
   */
  private final FocusManager parent;

  /** Focusables in this domain. */
  private final LinkedSequence<Focusable> focusOrder = LinkedSequence.create();

  /** Currently selected focusable. May be null. */
  private Focusable selected;

  /**
   * True iff this focus manager is focused (i.e., is between {@link #onFocus()}
   * and {@link #onBlur()}).
   */
  private boolean focused;

  private FocusManager(FocusManager parent, boolean focused) {
    this.parent = parent;
    this.focused = focused;
  }

  /** @return the root focus manager, that handles key events on the page. */
  public static FocusManager getRoot() {
    return ROOT;
  }

  /**
   * Creates a child focus manager, adding it to this focus manager's domain of
   * focusables.
   *
   * @return the new child manager.
   */
  public FocusManager createChild() {
    FocusManager child = new FocusManager(this, false);
    focusOrder.append(child);
    return child;
  }

  /**
   * Adds a focusable to this domain.
   *
   * @param focusable focusable to add
   */
  public void add(Focusable focusable) {
    focusOrder.append(focusable);
  }

  /**
   * Removes a focusable from this domain.
   *
   * @param focusable focusable to remove
   */
  public void remove(Focusable focusable) {
    focusOrder.remove(focusable);
  }

  /**
   * Moves focus to the next focusable in this domain.
   */
  public void selectNext() {
    select(focusOrder.getNext(selected));
  }

  /**
   * Moves focus to the previous focusable in this domain.
   */
  public void selectPrevious() {
    select(focusOrder.getPrevious(selected));
  }

  /**
   * Ensures that this manager has global focus. All ancestor managers become
   * selected and focused, and this manager's selected focusable gains
   * application focus.
   */
  public void ensureGlobalFocus() {
    if (!focused) {
      if (parent != null) {
        parent.select(this);
        parent.ensureGlobalFocus();
      }
      assert focused;
      // The onFocus handler already propagates the onFocus event to the current
      // focusable.
    }
  }

  /**
   * Sets the selected focusable.
   *
   * @param focusable focusable to select
   */
  public void select(Focusable focusable) {
    Preconditions.checkArgument(focusable == null || focusOrder.contains(focusable));
    if (ValueUtils.equal(selected, focusable)) {
      // No-op.
      return;
    }

    if (focused && selected != null) {
      selected.onBlur();
    }
    selected = focusable;
    if (focused && selected != null) {
      selected.onFocus();
    }
  }

  @Override
  public void onFocus() {
    Preconditions.checkState(!focused);
    focused = true;
    if (selected != null) {
      selected.onFocus();
    }
  }

  @Override
  public void onBlur() {
    Preconditions.checkState(focused);
    if (selected != null) {
      selected.onBlur();
    }
    focused = false;
  }

  @Override
  public boolean onKeySignal(KeyCombo key) {
    return (selected != null) ? selected.onKeySignal(key) : false;
  }

  /**
   * Special panel to grab events for the whole page, and dispatch them to the
   * top-level handlers.
   */
  private final static class DocumentPanel extends ComplexPanel
      implements KeyDownHandler, KeyUpHandler, KeyPressHandler {

    private final KeySignalHandler globalHandler;

    private int statDialogCondition = 0;

    private DocumentPanel(KeySignalHandler handler) {
      this.globalHandler = handler;
    }

    /**
     * Installs a key handler for key events on this window.
     *
     * @param handler handler to receive key events.
     */
    static void install(KeySignalHandler handler) {
      //
      // NOTE: There are three potential candidate elements for sinking keyboard
      // events: the window, the document, and the document body. IE7 does not
      // fire events on the window element, and GWT's RootPanel is already a
      // listener on the body, leaving the document as the only cross-browser
      // whole-window event-sinking 'element'.
      //
      DocumentPanel panel = new DocumentPanel(handler);
      panel.setElement(Document.get().<Element>cast());
      panel.addDomHandler(panel, KeyDownEvent.getType());
      panel.addDomHandler(panel, KeyPressEvent.getType());
      panel.addDomHandler(panel, KeyUpEvent.getType());
      RootPanel.detachOnWindowClose(panel);
      panel.onAttach();
    }

    @Override
    public void onKeyDown(KeyDownEvent event) {
      dispatch(event);
    }

    @Override
    public void onKeyUp(KeyUpEvent event) {
      dispatch(event);
    }

    @Override
    public void onKeyPress(KeyPressEvent event) {
      dispatch(event);
    }

    private void dispatch(KeyEvent<?> event) {
      if (checkStatDialogCondition(event)) {
        StatDialog.show();
      } else {
  Timer timer = null;
        if (Timing.isEnabled()) {
          Timing.enterScope();
          Timing.setScopeValue(ExecutionTree.class, new ExecutionTree());
          timer = Timing.start("Key event dispatch");
        }
        try {
          // Only respond to key events on the body element. Otherwise, the key
          // event was probably targeted to some editable input element, and that
          // should own the events.
          NativeEvent realEvent = event.getNativeEvent();
          Element target = realEvent.getEventTarget().cast();
          if (!"body".equals(target.getTagName().toLowerCase())) {
            return;
          }
          // Test that the event is meaningful (and stop bubbling if it is not).
          SignalEvent signal = SignalEventImpl.create(realEvent.<Event>cast(), true);
          if (signal != null) {
            KeyCombo key = EventWrapper.getKeyCombo(signal);
            if (globalHandler.onKeySignal(key)) {
              event.preventDefault();
            }
          }
        } finally {
          Timing.stop(timer);
          Timing.exitScope();
        }
      }
    }

    // Press and release one by one Ctrl, Alt and Ctrl again to show statistic dialog.
    private final static int statDialogKeyCodeSequence[] = { KeyCodes.KEY_CTRL, KeyCodes.KEY_ALT, KeyCodes.KEY_CTRL };

    private static boolean checkStatDialogCondition(int state, int keyCode, boolean down) {
      return statDialogKeyCodeSequence[state/2] == keyCode && ((state & 1) == 0) == down;
    }

    private boolean checkStatDialogCondition(KeyEvent<?> event) {
      boolean down = event instanceof KeyDownEvent;
      if (checkStatDialogCondition(statDialogCondition, event.getNativeEvent().getKeyCode(), down)) {
        if (statDialogCondition/2 == statDialogKeyCodeSequence.length-1) {
          statDialogCondition = 0;
          return true;
        }
        statDialogCondition++;
      } else if (statDialogCondition != 0) {
        statDialogCondition = checkStatDialogCondition(0, event.getNativeEvent().getKeyCode(), down) ? 1 : 0;
      }
      return false;
    }
  }
}
TOP

Related Classes of org.waveprotocol.wave.client.wavepanel.event.FocusManager

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.