Package DisplayProject

Source Code of DisplayProject.FocusHelper

/*
Copyright (c) 2003-2008 ITerative Consulting Pty Ltd. All Rights Reserved.

Redistribution and use in source and binary forms, with or without modification, are permitted
provided that the following conditions are met:

o Redistributions of source code must retain the above copyright notice, this list of conditions and
the following disclaimer.

o Redistributions in binary form must reproduce the above copyright notice, this list of conditions
and the following disclaimer in the documentation and/or other materials provided with the distribution.

o This jcTOOL Helper Class software, whether in binary or source form may not be used within,
or to derive, any other product without the specific prior written permission of the copyright holder


THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING,
BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.



*/
package DisplayProject;

import java.awt.Component;
import java.awt.Container;
import java.awt.KeyboardFocusManager;
import java.awt.Window;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.List;
import java.util.Stack;

import javax.swing.ButtonGroup;
import javax.swing.InputVerifier;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JFormattedTextField;
import javax.swing.JPasswordField;
import javax.swing.JRadioButtonMenuItem;
import javax.swing.JTabbedPane;
import javax.swing.SwingUtilities;
import javax.swing.table.TableCellEditor;
import javax.swing.text.JTextComponent;

import org.apache.log4j.Logger;

import DisplayProject.actions.WidgetState;
import DisplayProject.controls.ArrayField;
import DisplayProject.controls.AutoResizingComboBox;
import DisplayProject.controls.MultiLineTextField;
import DisplayProject.events.ClientEventManager;
import DisplayProject.table.ArrayFieldCellHelper;
import Framework.EventHandle;
import Framework.EventManager;
import Framework.ForteKeyboardFocusManager;
import Framework.ParameterHolder;
import Framework.PurgeEventsHandler;

/**
* This class helps mirror the forte event chains in Java. There are several actions in Java
* that do not mirror the same events as forte does, in particular with the AfterValueChange
* events. For example, using the default button in Java does not trigger a FocusLoss event
* on the focus owner and thus AfterValueChange events are not fired.<p>
* <p>
* To get around this situation, this class knows how to properly handle the various actions
* that may not fire a focus loss event and adjusts the event chains accordingly. For example,
* on a window closing, it will automatically fire an AfterValueChange event and then post the
* AfterFinalize event. These are done in the same chain so the event manager can abort the
* whole chain if needed.<p>
* <p>
* This class also supports some purging actions. When an event is purged, the EventManager
* determines who is interested in the purge and notifies them. Widgets can register a purge
* action on this class, then when the purge occurs these actions will be invoked on the EDT.
* Hence, components can re-request focus, set the caret position, etc.<p>
* <p>
* Note: This class should be used only by infrastructure components, and should not be required
* from user (application) code.
*
* @author Tim
*
*/
public class FocusHelper {

  private static Logger _log = Logger.getLogger(FocusHelper.class);

  /**
   * This method is invoked when a window is closing via the X on the right of
   * the window, unless the policy is SC_ENABLEDFINALIZE in which case
   * requestFinalize should be called.
   *
   * @param win -
   *            the window that is closing
   */
  public static void windowClosing(Window win, EventHandle[] extraEvents) {
    if (shouldAllowEvents(win)) {
      EventManager.startEventChain();
      _postAfterValueChangeEventsIfNeeded(win);
      ClientEventManager.postEvent(win, "Shutdown");
      if (extraEvents != null) {
        for (int i = 0; i < extraEvents.length; i++) {
          // TF:24/04/2008: Changed this to use the client event manager to get the right order for events
          ClientEventManager.postEvent(extraEvents[i]);
        }
      }
      EventManager.endEventChain();
    }
  }

  private static void _postBeforeFocusLossEventsIfNeeded(Component focusOwner) {
    if (focusOwner != null) {
      if (focusOwner instanceof DataField) {
        ((DataField) focusOwner).postBeforeFocusLoss(Constants.FC_KEYNEXT);
      }
      else if (focusOwner instanceof PasswordDataField) {
        ((PasswordDataField) focusOwner).postBeforeFocusLoss(Constants.FC_KEYNEXT);
      }
      else if (focusOwner instanceof MultiLineTextField) {
        ((MultiLineTextField)focusOwner).postAfterValueChange();
      }
    }
  }

  private static void _postAfterValueChangeEventsIfNeeded(Component focusOwner) {
    if (focusOwner != null) {
      // TF:01/12/2009:DET-134:We only allow the editor to commit and post the appropriate events if we're in update mode
            // TF:04/12/2009:DET-139:Corrected this to be the true widget state, ie the worst state out of this widget and its parents
      if (WidgetState.getWorstState(focusOwner) == Constants.FS_UPDATE) {
        // TF:7/4/08: If the component which has focus is currently a table cell editor then
        // we need to ensure that the value has been committed to the underlying model. Otherwise accelerators
        // will still have the wrong value since we haven't processed the focus loss event.
        if (focusOwner instanceof JTextComponent) {
          ArrayField table = ArrayFieldCellHelper.getArrayField(focusOwner);
          if (table != null) {
            if (ArrayFieldCellHelper.getArrayEditorComponent(table) == focusOwner) {
              // TF:08/06/2008:LIB-17:If this is a formatted field, we must use the getValue method,
              // otherwise we will potentially have formatting errors on masked fields.
              if (focusOwner instanceof JFormattedTextField) {
                JFormattedTextField formattedField = (JFormattedTextField)focusOwner;
                try {
                  formattedField.commitEdit();
                  //GK/DK: convert column index to a model index to set value
                  table.setValueAt(formattedField.getValue(), table.getEditingRow(), table.getEditingColumn());
                }
                catch (ParseException e) {
                  _log.error("Unhandled parse exception committing field edit: ", e);
                }
              }
              else if (focusOwner instanceof JPasswordField) {
                table.setValueAt(new String(((JPasswordField)focusOwner).getPassword()), table.getEditingRow(), table.getEditingColumn());
              }
              else {
                table.setValueAt(((JTextComponent)focusOwner).getText(), table.getEditingRow(), table.getEditingColumn());
              }
            }
          }
        }
        if (focusOwner instanceof DataField) {
          DataField df = (DataField)focusOwner;
          if (!df.isValidateOnKeystroke()) {
            df.postAfterValueChange();
          }
        }
        else if (focusOwner instanceof PasswordDataField) {
          if (!((PasswordDataField)focusOwner).isValidateOnKeystroke()) {
            ((PasswordDataField) focusOwner).postAfterValueChange();
          }
        }
        else if (focusOwner instanceof MultiLineTextField) {
          if (!((MultiLineTextField)focusOwner).isValidateOnKeystroke()) {
            ((MultiLineTextField)focusOwner).postAfterValueChange();
          }
        }
      }
      // Simulate the focus owner change irrespective of widget state.
      ((ForteKeyboardFocusManager)KeyboardFocusManager.getCurrentKeyboardFocusManager()).simulateFocusOwnerChange();
    }
  }

  private static void _postAfterValueChangeEventsIfNeeded(Window win) {
    Component focusOwner = win.getFocusOwner();
    _postAfterValueChangeEventsIfNeeded(focusOwner);
  }

  /**
   * Do a request finalise without checking if we have a glass pane up or not.
   * This is useful for programatically calling requestFinalize.  CraigM: 22/04/2008.
   *
   * @param win
   * @param reasonCode
   */
  public static void requestFinalizeNoCheck(Window win, int reasonCode) {
    EventManager.startEventChain();
    _postAfterValueChangeEventsIfNeeded(win);
    Hashtable<String, Object> params = new Hashtable<String, Object>();
    params.put("ReasonCode", new ParameterHolder(reasonCode));
    boolean isRegistered = EventManager.isAnyThreadRegistered(win, "AfterFinalize");

    ClientEventManager.postEvent(win, "AfterFinalize", params);
    EventManager.endEventChain();

    // TF:23/06/2008: Interrupt the thread and close the window if no one has been listening the event.
    //The way will fix a critical but to hold on opened window after exception.
    //The error caused by unhandled exceptions in forte source.
    //Forte generates CancelException if user trying to close the window twice.
    if (!isRegistered && win != null) {
      Thread t = WindowManager.getOwningThread(win);
      WindowManager.deregisterWindow(win);
      // TF:07/07/2008:Added in some safety checks
      if (t != null && t.isAlive() && !t.isInterrupted()) {
        try {
          t.interrupt();
        }
        catch (Exception e) {}
      }
    }
  }

  /**
   * This method is called when a window is closed via the X with a shutdown
   * policy of SC_ENABLEDFINALIZE, or requestFinalise is explicitly called.
   *
   * @param win
   * @param reasonCode
   */
  public static void requestFinalize(Window win, int reasonCode) {
    if (shouldAllowEvents(win)) {
      FocusHelper.requestFinalizeNoCheck(win, reasonCode);
    }
  }

  /**
   * We don't allow events of a GUI nature to a window if there is a visible glass pane over the
   * window because in theory the window is disabled
   * @param comp
   * @return
   */
  public static boolean shouldAllowEvents(JComponent comp) {
    return shouldAllowEvents(comp.getTopLevelAncestor());
  }

  public static boolean shouldAllowEvents(Container win) {
    Component glass = UIutils.getGlassPane(win);
    if (glass != null && glass.isVisible())
      return false;
    else {
      Component focusOwner = KeyboardFocusManager.getCurrentKeyboardFocusManager().getFocusOwner();
      if (focusOwner != null && focusOwner instanceof DataField) {
        DataField df = (DataField)focusOwner;
        InputVerifier verifier = df.getInputVerifier();
        if (verifier != null) {
          return verifier.shouldYieldFocus(df);
        }
      }
    }
    return true;
  }

  public static void buttonClick(JButton pComp, EventHandle pHandle) {
    if (shouldAllowEvents(pComp)) {
      Component focusOwner = KeyboardFocusManager.getCurrentKeyboardFocusManager().getFocusOwner();
      EventManager.startEventChain();
      if (focusOwner == pComp) {
        // The events will be in the right sequence, just post the event
        // TF:24/04/2008: Changed this to use the client event manager to get the right order for events
        ClientEventManager.postEvent(pHandle);
      }
      else {
        // This is done through an accelerator key, default button, etc
        // Thus we will have lost the events. We need to set the focus onto
        // component which
        // should trigger the afterValueChange and the focusLoss messages

        // Note that we cannot guarantee the focus events will arrive in time, so we
        // must explicitly post them. But we set the ignore flag to true so the
        // real events will be skipped.
        _postAfterValueChangeEventsIfNeeded(focusOwner);
        _postBeforeFocusLossEventsIfNeeded(focusOwner);
        // TF:24/04/2008: Changed this to use the client event manager to get the right order for events
        ClientEventManager.postEvent(pHandle);

        if (focusOwner instanceof DataField) {
          ((DataField) focusOwner).disableEvents(true);
        }
        pComp.requestFocus();
      }
      EventManager.endEventChain();
    }
  }

  public static void menuActivate(JComponent owner, List<EventHandle> pEventsToPost) {
    if (shouldAllowEvents(owner)) {
      // TF:03/06/2008:Changed this to use the permanent focus owner. If this change is not
      // not made (leaving it as the focus owner) then if a menu is activated using the mouse
      // or Alt-keys (ie, not the accelerator) whilst a data field inside an array field is
      // active, then the focus owner will be the root pane, not the data field, and this will
      // cause the data not to be mapped properly to the underlying model prior to invoking the
      // activate code. However, the PermanentFocusOwner is the data field, so we need to use
      // this. See LIB-17 for more details.
      Component focusOwner = KeyboardFocusManager.getCurrentKeyboardFocusManager().getPermanentFocusOwner();
      EventManager.startEventChain();
      // if the focus owner is not the component, this has been activated through menu shortcuts
      // so we need to post the normal focus loss event as part of the chain
      if (focusOwner != owner) {
        _postAfterValueChangeEventsIfNeeded(focusOwner);
      }
      if (pEventsToPost != null) {
        for (EventHandle event : pEventsToPost) {
          // TF:24/04/2008: Changed this to use the client event manager to get the right order for events
          ClientEventManager.postEvent(event);
        }
      }
      if (owner instanceof JRadioButtonMenuItem) {
        ButtonGroup parent = ((ButtonGroup) ((JRadioButtonMenuItem) owner).getClientProperty("qq_bg"));
        //          toData();
        ClientEventManager.postEvent(parent, "AfterValueChange");
      }

      EventManager.endEventChain();
    }
  }

  /**
   * Invoked when a function key is pressed. In this case there definitely
   * will not be a component that is obtaining and controlling the focus, so
   * we get the current one off the keyboardFocusManager
   *
   * @param pEventToPost
   */
  public static void functionKeyActivate(EventHandle pEventToPost) {
    Component focusOwner = KeyboardFocusManager.getCurrentKeyboardFocusManager().getFocusOwner();
    if (focusOwner instanceof JComponent) {
      if (shouldAllowEvents((JComponent)focusOwner)) {
        EventManager.startEventChain();
        _postAfterValueChangeEventsIfNeeded(focusOwner);
        // TF:24/04/2008: Changed this to use the client event manager to get the right order for events
        ClientEventManager.postEvent(pEventToPost);
        EventManager.endEventChain();
      }
    }
    else if (focusOwner instanceof Container) {
      if (shouldAllowEvents((Container)focusOwner)) {
        EventManager.startEventChain();
        _postAfterValueChangeEventsIfNeeded(focusOwner);
        // TF:24/04/2008: Changed this to use the client event manager to get the right order for events
        ClientEventManager.postEvent(pEventToPost);
        EventManager.endEventChain();
      }
    }
    else if (focusOwner == null) {
      // TF:8/10/07: If we have no focus owner we still need to post the original function event
      EventManager.startEventChain();
      // TF:24/04/2008: Changed this to use the client event manager to get the right order for events
      ClientEventManager.postEvent(pEventToPost);
      EventManager.endEventChain();
    }
  }
  private static class PurgeEventsMgr implements PurgeEventsHandler {
    private class ConditionalRunnable {
      private Object object;
      private String event;
      private Runnable runnable;

      public ConditionalRunnable(Runnable pRunnable, Object pObject, String pEvent) {
        this.object = pObject;
        this.event = pEvent;
        this.runnable = pRunnable;
      }
      /**
       * @return the event
       */
       public String getEvent() {
         return this.event;
       }
       /**
        * @return the runnable
        */
       public Runnable getRunnable() {
         return this.runnable;
       }
       /**
        * @return the object
        */
       public Object getObject() {
         return this.object;
       }

       /**
        * Helper method for the debugger to easily see what's going on.
        */
       @Override
       public String toString() {
         return this.runnable.toString();
       }
    }
    /**
     * A hashtable keyed by the chain id, storing an array of Runnables
     */
    private Hashtable<Integer, ArrayList<ConditionalRunnable>> actionLists = new Hashtable<Integer, ArrayList<ConditionalRunnable>>();
    public PurgeEventsMgr() {
      EventManager.setPurgeEventsHandler(this);
    }

    /**
     * The event chain has been finished processing, so we need to remove all the events from
     * the list to prevent a memory drain
     */
    public synchronized void chainFinished(int pChainId) {
      if (_log.isDebugEnabled()) {
        _log.debug("finishing chain " + pChainId);
      }
      // Clear this entry out of the hashtable
      this.actionLists.remove(new Integer(pChainId));
      if (_log.isDebugEnabled()) {
        for (Iterator<Integer> it = this.actionLists.keySet().iterator(); it.hasNext();) {
          Integer key = it.next();
          _log.debug("   Still opened chain: " + key + ", size = " + this.actionLists.get(key).size());
        }
      }
    }

    /**
     * Execute all the pending actions. Note that when this method has finished all the events
     * associated with the event we are purging will have been fired and posted to the event
     * manager. This includes focus change event, spurious tab selects, etc. This is critical -
     * it means that once this event terminates, the event manager is free to remove all events
     * currently in the event queue for the thread that invokes this method. This is possible
     * <b>only</b> because the pending events are processed on the EDT and that the java event
     * mechanism is really a callback, so that when the focus is transfered the callbacks for the
     * focus listeners get notified immediately, which then post the events onto the the event
     * manager. The event manager immediately places these events in the queue of the interested
     * parties before returning.
     */
    public boolean purgeEvents(final int pChainId, final ArrayList<EventHandle> pEventsToPurge) {
      _log.debug("Purging chain " + pChainId + " with " + pEventsToPurge.size() + " events");
      final ArrayList<ConditionalRunnable> items = (ArrayList<ConditionalRunnable>)actionLists.get(new Integer(pChainId));
      if (items != null && items.size() > 0) {
        // Clear the pending events first
        UIutils.processGUIActions();
        UIutils.invokeOnGuiThread(new Runnable() {
          public void run() {
            // TF:12/10/07:Made sure to disable the event manager posting new events as we rollback the old ones
            boolean isEnabled = EventManager.isPostingEnabled();
            EventManager.disableEventPosting();
            try {
              for (Iterator<EventHandle> it1 = pEventsToPurge.iterator(); it1.hasNext();) {
                EventHandle handle = (EventHandle)it1.next();
                for (ConditionalRunnable r : items) {
                  if (r.getEvent() != null &&
                      r.getEvent().equals(handle.getName()) &&
                      r.getObject().equals(handle.getSourceObject())) {
                    _log.debug("   Purging chain " + pChainId + ":" + r.getRunnable());
                    r.getRunnable().run();
                  }
                }
              }
              // Pass 2: Execute any generic runnables.
              boolean hasTabFocus = false;
              for (ConditionalRunnable r : items) {
                if (r.getEvent() == null) {
                  _log.debug("   Purging chain " + pChainId + ":" + r.getRunnable());
                  if (r.getRunnable() instanceof TabFocus){
                    /*
                     * PM:11/10/07
                     * if we have an instance of a TabFocus we want all subsequent
                     * rollback actions to be placed in the EDT queue to be done after the
                     * tab event is complete.
                     */
                    hasTabFocus = true;
                    r.getRunnable().run();
                  } else {
                    if (hasTabFocus){
                      SwingUtilities.invokeLater(r.getRunnable());
                    } else {
                      r.getRunnable().run();
                    }
                  }
                }
              }
            }
            finally {
              if (isEnabled) {
                EventManager.enableEventPosting();
              }
            }
          }
        });
        return true;
      }
      return false;
    }

    public void startEventChainDefinition(int pChainId) {
    }
    public void endEventChainDefinition(int pChainId) {
    }

    public synchronized void addPurgeAction(Runnable r, Object pObject, String pEvent) {
      int currentId = EventManager.getCurrentEventChainId();
      if (_log.isDebugEnabled()) {
        _log.debug("Adding To event chain " + currentId + " for event " + pEvent + ", rollback action " + r.toString());
      }
      if (currentId != EventManager.cNO_CHAIN) {
        ArrayList<ConditionalRunnable> list = (ArrayList<ConditionalRunnable>)actionLists.get(new Integer(currentId));
        if (list == null) {
          list = new ArrayList<ConditionalRunnable>();
          actionLists.put(new Integer(currentId), list);
        }
        list.add(new ConditionalRunnable(r, pObject, pEvent));
      }
    }

    public void addPurgeAction(Runnable r) {
      this.addPurgeAction(r, null, null);
    }
  }

  private static PurgeEventsMgr purgeEventsMgr = new PurgeEventsMgr();
  public static void addPurgeAction(Runnable r) {
    purgeEventsMgr.addPurgeAction(r);
  }

  public static void addPurgeAction(Runnable r, Object o, String pEvent) {
    purgeEventsMgr.addPurgeAction(r, o, pEvent);
  }

  public static void chainFinished(int pChainId) {
    purgeEventsMgr.chainFinished(pChainId);
  }

  /*
   * request focus in field
   */
  protected static class FocusInField implements Runnable {
    private JComponent comp;

    public FocusInField(JComponent component){
      this.comp = component;
    }

    public void run() {
      if (comp == null) return;
      if (_log.isDebugEnabled())
        _log.debug("   --> requesting focus on  " +comp.getName());

      if (!comp.requestFocus(false)){
        _log.debug("   ---> focus not granted" );
        _log.debug("   ----> component isVisible: " + comp.isVisible());
        _log.debug("   ----> component isShowing: " + comp.isShowing());
        _log.debug("   ----> component isEnabled: " + comp.isEnabled());
        _log.debug("   ----> component isFocusable: " + comp.isFocusable());
      }

    }
    @Override
    public String toString() {
      return "Setting focus to " + comp;
    }
  }
  /*
   * ArrayField focus
   */
  protected static class ArrayFieldFocus implements Runnable{
    private ArrayField comp;
    private int row;
    private int column;
    public ArrayFieldFocus(ArrayField component){
      this.comp = component;
      //PM:13/10/07 removed to match the changes to array field events
      //        CellRowCol prev = component.getPreviousCell();
      row = /*prev == null ?*/ component.getEditingRow()/* : prev.row*/;
      column = /*prev == null ?*/ component.getEditingColumn() /*: prev.col*/;
    }

    public void run() {
      // TF:28/06/2008:If there is an active editor and it's not for the correct cell
      // (ie the one we need to go back to), we must cancel the edit. If we just stop
      // editing (for example, using change selection) and the user has clicked on a
      // prior row in the table to the one we're purging, it is possible that the
      // value that is being purged will be copied across to the selected cell!
      TableCellEditor tce = comp.getCellEditor();
      if (tce != null && (comp.getEditingRow() != row || comp.getEditingColumn() != column)) {
        tce.cancelCellEditing();
      }
      //      comp.editCellAt(row, column);
      _log.debug("   --> changing selection on " +comp.getName() + " to [" + row + "," + column + "]");
      comp.changeSelection(row, column, false, false);

    }
    @Override
    public String toString() {
      return "Editing cell " + row + "," + column + " on " + comp;
    }

  }
  /*
   * Tab folder focus
   */
  protected static class TabFocus implements Runnable{
    private JTabbedPane comp;
    private int index;
    public TabFocus(JTabbedPane component, int index){
      this.comp = component;
      this.index = index;
    }

    public void run() {
      try {
        if (comp.getSelectedIndex() != index){
          _log.debug("   --> changing index on " +comp.getName() + " to [" + index  + "]");
          comp.setSelectedIndex(index);
        }
      }
      catch (Exception e) {
        // Do nothing, just allow the tab not to change
        _log.error("Could not change to tab " + index+ " on tab pane " +comp.getName(), e );
      }
    }
    @Override
    public String toString() {
      return "activating tab " + index + " on " + comp;
    }

  }
  /*
   * TextField focus
   */
  protected static class TextFieldFocus implements Runnable{
    private JTextComponent comp;
    private int start;
    private int end;
    private int caret;
    private String text;

    public TextFieldFocus(JTextComponent component, int start, int end, int caret, String text){
      this.comp = component;
      this.start = start;
      this.end = end;
      this.caret = caret;
      this.text = text;
    }

    public void run() {
      comp.setText(text);
      comp.select(start, end);
      comp.setCaretPosition(caret);
    }
    @Override
    public String toString() {
      return "Setting text to \"" + text + "\" and selecting " + start + " to " + end + " on " + comp;
    }

  }
  /*
   * DropList and FillInFields focus
   */
  protected static class DropFillinFocus implements Runnable{
    private AutoResizingComboBox comp;
    private Object index;
    public DropFillinFocus(AutoResizingComboBox component, Object oldValue){
      this.comp = component;
      this.index = oldValue;
    }

    public void run() {
      comp.setSelectedItemWithoutPosting(index);
    }
    @Override
    public String toString() {
      return "Selecting " + index + " on " + comp;
    }

  }
  /**
   * this method sets up the roll back actions by creating a list. This list is processed in reverse order
   * @param comp
   */
  public static void addSetFocusPurgeAction(JComponent comp) {
    // TF:3/4/09:Ensured that we are in an event chain prior to registering the rollback actions
    if (EventManager.isInEventChain()) {
      Stack<Runnable> s = new Stack<Runnable>();
      final JComponent thisComp = comp;
      _log.debug("addSetFocusPurgeAction() on " +comp.getName() );
      s.push(new FocusInField(comp));

      JComponent oldComp = null;
      while (comp != null) {

        /*
         * ArrayField
         */
        ArrayField af = ArrayFieldCellHelper.getArrayField(comp);
        if (af != null) {
          s.push(new ArrayFieldFocus(af));
        }
        /*
         * TabFolder
         */
        if (comp instanceof JTabbedPane) {
          JTabbedPane tp = (JTabbedPane)comp;
          // NB: Do not use getSelectedIndex() because the index has already changed at this point
          int index = tp.indexOfComponent(oldComp);
          s.push(new TabFocus(tp, index));
        }
        /*
         * TextField
         */
        else if (comp instanceof JTextComponent) {
          JTextComponent tc = (JTextComponent)comp;
          int start = tc.getSelectionStart();
          int end = tc.getSelectionEnd();
          int caret = tc.getCaretPosition();
          String text = tc.getText();
          s.push(new TextFieldFocus(tc, start, end, caret, text));
        }
        /*
         * DropList and FillInFields
         */
        else if (comp instanceof AutoResizingComboBox) {
          AutoResizingComboBox acr = (AutoResizingComboBox)comp;
          Object oldValue = acr.getPreviousValue();
          s.push(new DropFillinFocus(acr, oldValue));
        }
        oldComp = comp;
        Container c = (Container)comp.getParent();
        if (c instanceof JComponent) {
          comp = (JComponent)c;
        }
        else {
          break;
        }
      }
      /*
       * request the focus in window
       */
      s.push(new Runnable() {
        public void run() {
          thisComp.requestFocusInWindow();
        }
        @Override
        public String toString() {
          return "Setting focus to " + thisComp;
        }
      });

      // Now push the actions on in reverse order, allowing us to do things like setting the tab pane prior to setting the focus
      while (!s.isEmpty()) {
        addPurgeAction((Runnable)s.pop());
      }
    }
    else {
      _log.error("addSetFocusPurgeAction called when not in an event chain. These purge actions will not be added.");
    }
  }

  /**
   * Set the focus back onto the passed component. This differs from the normal requestFocus() by
   * ensuring it's on the active tab panes going up the component hierarchy.
   * @param comp
   */
  public static void setFocus(final JComponent comp) {
    UIutils.invokeOnGuiThread(new Runnable() {
      public void run() {
        // First, set all the tabs properly
        JComponent thisComp = (JComponent)comp.getParent();
        JComponent oldComp = comp;
        while (thisComp != null) {
          if (thisComp instanceof JTabbedPane) {
            ((JTabbedPane)thisComp).setSelectedComponent(oldComp);

            // CraigM:12/08/2008 - Have to manually tell the tab panel to show itself
            ((JTabbedPane)thisComp).getSelectedComponent().setVisible(true);

            // CraigM:12/08/2008 - When switching tabs, the panel tried to set focus.
            new Thread(new Runnable() {
              public void run() {
                // Wait for all the other focus requests to complete before moving the focus
                UIutils.waitForEDTToBeIdle();

                SwingUtilities.invokeLater(new Runnable() {
                  public void run() {
                    comp.requestFocus();
                  };
                });
              }
            }).start();
          }
          oldComp = thisComp;
          if (thisComp.getParent() instanceof JComponent) {
            thisComp = (JComponent)thisComp.getParent();
          }
          else {
            thisComp = null;
          }
        }
        // Ensure the window has focus too
        comp.requestFocusInWindow();
      }
    });
  }

}
TOP

Related Classes of DisplayProject.FocusHelper

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.