All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.intellij.openapi.wm.impl.FocusManagerImpl Maven / Gradle / Ivy

Go to download

A packaging of the IntelliJ Community Edition platform-impl library. This is release number 1 of trunk branch 142.

The newest version!
/*
 * Copyright 2000-2014 JetBrains s.r.o.
 *
 * 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 com.intellij.openapi.wm.impl;

import com.intellij.ide.IdeEventQueue;
import com.intellij.ide.UiActivity;
import com.intellij.ide.UiActivityMonitor;
import com.intellij.internal.focus.FocusTracesAction;
import com.intellij.openapi.Disposable;
import com.intellij.openapi.actionSystem.DataContext;
import com.intellij.openapi.actionSystem.PlatformDataKeys;
import com.intellij.openapi.application.Application;
import com.intellij.openapi.application.ApplicationActivationListener;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.application.ModalityState;
import com.intellij.openapi.application.ex.ApplicationManagerEx;
import com.intellij.openapi.components.impl.ServiceManagerImpl;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.ui.popup.JBPopup;
import com.intellij.openapi.util.*;
import com.intellij.openapi.util.registry.Registry;
import com.intellij.openapi.wm.*;
import com.intellij.openapi.wm.ex.IdeFocusTraversalPolicy;
import com.intellij.openapi.wm.ex.LayoutFocusTraversalPolicyExt;
import com.intellij.reference.SoftReference;
import com.intellij.ui.FocusTrackback;
import com.intellij.util.containers.WeakValueHashMap;
import com.intellij.util.ui.UIUtil;
import gnu.trove.TIntIntHashMap;
import gnu.trove.TIntIntProcedure;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import javax.swing.*;
import java.awt.*;
import java.awt.event.FocusEvent;
import java.awt.event.KeyEvent;
import java.awt.event.WindowEvent;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.lang.ref.Reference;
import java.lang.ref.WeakReference;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.List;

public class FocusManagerImpl extends IdeFocusManager implements Disposable {
  private static final Logger LOG = Logger.getInstance(FocusManagerImpl.class);
  private static final UiActivity FOCUS = new UiActivity.Focus("awtFocusRequest");
  private static final UiActivity TYPEAHEAD = new UiActivity.Focus("typeahead");

  private final Application myApp;

  private FocusCommand myRequestFocusCmd;
  private final List myFocusRequests = new ArrayList();

  private final List myToDispatchOnDone = new ArrayList();

  private Reference myLastForcedRequest;

  private FocusCommand myFocusCommandOnAppActivation;
  private ActionCallback myCallbackOnActivation;
  private final boolean isInternalMode = ApplicationManagerEx.getApplicationEx().isInternal();
  private final LinkedList myRequests = new LinkedList();

  private final IdeEventQueue myQueue;
  private final KeyProcessorContext myKeyProcessorContext = new KeyProcessorContext();

  private long myCmdTimestamp;
  private long myForcedCmdTimestamp;

  private final EdtAlarm myFocusedComponentAlarm;
  private final EdtAlarm myForcedFocusRequestsAlarm;

  private final SimpleTimer myTimer = SimpleTimer.newInstance("FocusManager timer");
  
  private final EdtAlarm myIdleAlarm;
  private final Set myIdleRequests = new LinkedHashSet();

  private boolean myFlushWasDelayedToFixFocus;
  private ExpirableRunnable myFocusRevalidator;

  private final Set myValidFurtherRequestors = new HashSet();

  private final Set myTypeAheadRequestors = new HashSet();
  private final UiActivityMonitor myActivityMonitor;
  private boolean myTypeaheadEnabled = true;
  private int myModalityStateForLastForcedRequest;


  private class IdleRunnable extends EdtRunnable {
    @Override
    public void runEdt() {
      if (canFlushIdleRequests()) {
        flushIdleRequests();
      }
      else {
        if (processFocusRevalidation()) {
          if (isFocusTransferReady()) {
            flushIdleRequests();
          }
        }

        restartIdleAlarm();
      }
    }
  }

  private boolean canFlushIdleRequests() {
    Component focusOwner = getFocusOwner();
    return isFocusTransferReady()
           && !isIdleQueueEmpty()
           && !IdeEventQueue.getInstance().isDispatchingFocusEvent()
           && !(focusOwner == null && (!myValidFurtherRequestors.isEmpty() || myFocusRevalidator != null && !myFocusRevalidator.isExpired()));
  }

  private final Map myLastFocused = new WeakValueHashMap();
  private final Map myLastFocusedAtDeactivation = new WeakValueHashMap();

  private DataContext myRunContext;

  private final TIntIntHashMap myModalityCount2FlushCount = new TIntIntHashMap();

  private IdeFrame myLastFocusedFrame;

  @SuppressWarnings("UnusedParameters")  // the dependencies are needed to ensure correct loading order
  public FocusManagerImpl(ServiceManagerImpl serviceManager, WindowManager wm, UiActivityMonitor monitor) {
    myApp = ApplicationManager.getApplication();
    myQueue = IdeEventQueue.getInstance();
    myActivityMonitor = monitor;

    myFocusedComponentAlarm = new EdtAlarm();
    myForcedFocusRequestsAlarm = new EdtAlarm();
    myIdleAlarm = new EdtAlarm();

    final AppListener myAppListener = new AppListener();
    myApp.getMessageBus().connect().subscribe(ApplicationActivationListener.TOPIC, myAppListener);

    IdeEventQueue.getInstance().addDispatcher(new IdeEventQueue.EventDispatcher() {
      @Override
      public boolean dispatch(AWTEvent e) {
        if (e instanceof FocusEvent) {
          final FocusEvent fe = (FocusEvent)e;
          final Component c = fe.getComponent();
          if (c instanceof Window || c == null) return false;

          Component parent = UIUtil.findUltimateParent(c);

          if (parent instanceof IdeFrame) {
            myLastFocused.put((IdeFrame)parent, c);
          }
        }
        else if (e instanceof WindowEvent) {
          Window wnd = ((WindowEvent)e).getWindow();
          if (e.getID() == WindowEvent.WINDOW_CLOSED) {
            if (wnd instanceof IdeFrame) {
              myLastFocused.remove(wnd);
              myLastFocusedAtDeactivation.remove(wnd);
            }
          }
        }

        return false;
      }
    }, this);

    KeyboardFocusManager.getCurrentKeyboardFocusManager().addPropertyChangeListener("focusedWindow", new PropertyChangeListener() {
      @Override
      public void propertyChange(PropertyChangeEvent evt) {
        if (evt.getNewValue() instanceof IdeFrame) {
          myLastFocusedFrame = (IdeFrame)evt.getNewValue();
        }
      }
    });
  }

  @Override
  public IdeFrame getLastFocusedFrame() {
    return myLastFocusedFrame;
  }

  @Override
  @NotNull
  public ActionCallback requestFocus(@NotNull final Component c, final boolean forced) {
    return requestFocus(new FocusCommand.ByComponent(c, new Exception()), forced);
  }

  @Override
  @NotNull
  public ActionCallback requestFocus(@NotNull final FocusCommand command, final boolean forced) {
    assertDispatchThread();

    if (isInternalMode) {
      recordCommand(command, new Throwable(), forced);
    }
    final ActionCallback result = new ActionCallback();

    myActivityMonitor.addActivity(FOCUS, ModalityState.any());
    if (!forced) {

      UIUtil.invokeAndWaitIfNeeded(new Runnable() {
        @Override
        public void run() {
          if (!myFocusRequests.contains(command)) {
            myFocusRequests.add(command);
          }
        }
      });

      SwingUtilities.invokeLater(new Runnable() {
        @Override
        public void run() {
          resetUnforcedCommand(command);
          _requestFocus(command, forced, result);
        }
      });
    }
    else {
      _requestFocus(command, forced, result);
    }

    result.doWhenProcessed(new Runnable() {
      @Override
      public void run() {
        restartIdleAlarm();
      }
    });

    return result;
  }

  @NotNull
  public List getRequests() {
    return myRequests;
  }

  public void recordFocusRequest(Component c, boolean forced) {
    myRequests.add(new FocusRequestInfo(c, new Throwable(), forced));
    if (myRequests.size() > 200) {
      myRequests.removeFirst();
    }
  }

  private void recordCommand(@NotNull FocusCommand command, @NotNull Throwable trace, boolean forced) {
    if (FocusTracesAction.isActive()) {
      recordFocusRequest(command.getDominationComponent(), forced);
    }
  }

  private void _requestFocus(@NotNull final FocusCommand command, final boolean forced, @NotNull final ActionCallback result) {
    result.doWhenProcessed(new Runnable() {
      @Override
      public void run() {
        maybeRemoveFocusActivity();
      }
    });
    
    if (checkForRejectOrByPass(command, forced, result)) return;

    setCommand(command);
    command.setCallback(result);

    if (forced) {
      myForcedFocusRequestsAlarm.cancelAllRequests();
      setLastEffectiveForcedRequest(command);
    }

    SwingUtilities.invokeLater(new Runnable() {
      @Override
      public void run() {
        if (checkForRejectOrByPass(command, forced, result)) return;

        if (myRequestFocusCmd == command) {
          final TimedOutCallback focusTimeout =
            new TimedOutCallback(Registry.intValue("actionSystem.commandProcessingTimeout"),
                                        "Focus command timed out, cmd=" + command, command.getAllocation(), true) {
              @Override
              protected void onTimeout() {
                forceFinishFocusSettleDown(command, result);
              }
            };

          if (command.invalidatesRequestors()) {
            myCmdTimestamp++;
          }
          revalidateFurtherRequestors();
          if (forced) {
            if (command.invalidatesRequestors()) {
              myForcedCmdTimestamp++;
            }
            revalidateFurtherRequestors();
          }

          command.setForced(forced);
          command.run().doWhenDone(new Runnable() {
            @Override
            public void run() {
              UIUtil.invokeLaterIfNeeded(new Runnable() {
                @Override
                public void run() {
                  resetCommand(command, false);
                  result.setDone();
                }
              });
            }
          }).doWhenRejected(new Runnable() {
            @Override
            public void run() {
              result.setRejected();
              resetCommand(command, true);
            }
          }).doWhenProcessed(new Runnable() {
            @Override
            public void run() {
              if (forced) {
                myForcedFocusRequestsAlarm.addRequest(new SetLastEffectiveRunnable(), 250);
              }
            }
          }).notify(focusTimeout);
        }
        else {
          rejectCommand(command, result);
        }
      }
    });
  }

  private void maybeRemoveFocusActivity() {
    if (isFocusTransferReady()) {
      myActivityMonitor.removeActivity(FOCUS);
    }
  }

  private boolean checkForRejectOrByPass(@NotNull FocusCommand cmd, final boolean forced, @NotNull ActionCallback result) {
    if (cmd.isExpired()) {
      rejectCommand(cmd, result);
      return true;
    }

    final FocusCommand lastRequest = getLastEffectiveForcedRequest();

    if (!forced && !isUnforcedRequestAllowed()) {
      if (cmd.equals(lastRequest)) {
        resetCommand(cmd, false);
        result.setDone();
      }
      else {
        rejectCommand(cmd, result);
      }
      return true;
    }


    if (lastRequest != null && lastRequest.dominatesOver(cmd)) {
      rejectCommand(cmd, result);
      return true;
    }

    if (!Registry.is("focus.fix.lost.cursor")) {
      boolean doNotExecuteBecauseAppIsInactive =
        !myApp.isActive() && !canExecuteOnInactiveApplication(cmd) && Registry.is("actionSystem.suspendFocusTransferIfApplicationInactive");

      if (doNotExecuteBecauseAppIsInactive) {
        if (myCallbackOnActivation != null) {
          myCallbackOnActivation.setRejected();
          if (myFocusCommandOnAppActivation != null) {
            resetCommand(myFocusCommandOnAppActivation, true);
          }
        }

        myFocusCommandOnAppActivation = cmd;
        myCallbackOnActivation = result;

        return true;
      }
    }

    return false;
  }

  private void setCommand(@NotNull final FocusCommand command) {
    myRequestFocusCmd = command;

    UIUtil.invokeAndWaitIfNeeded(new Runnable() {
      @Override
      public void run() {
        if (!myFocusRequests.contains(command)) {
          myFocusRequests.add(command);
        }
      }
    });
  }

  private void resetCommand(@NotNull final FocusCommand cmd, boolean reject) {

    assertDispatchThread();

    if (cmd == myRequestFocusCmd) {
      myRequestFocusCmd = null;
    }

    final KeyEventProcessor processor = cmd.getProcessor();
    if (processor != null) {
      processor.finish(myKeyProcessorContext);
    }

    UIUtil.invokeAndWaitIfNeeded(new Runnable() {
      @Override
      public void run() {
        myFocusRequests.remove(cmd);
      }
    });

    if (reject) {
      ActionCallback cb = cmd.getCallback();
      if (cb != null && !cb.isProcessed()) {
        cmd.getCallback().setRejected();
      }
    }
  }

  private void resetUnforcedCommand(@NotNull final FocusCommand cmd) {
    UIUtil.invokeAndWaitIfNeeded(new Runnable() {
      @Override
      public void run() {
        myFocusRequests.remove(cmd);
      }
    });
  }

  private static boolean canExecuteOnInactiveApplication(@NotNull FocusCommand cmd) {
    return cmd.canExecuteOnInactiveApp();
  }

  private void setLastEffectiveForcedRequest(@Nullable FocusCommand command) {
    myLastForcedRequest = command == null ? null : new WeakReference(command);
    myModalityStateForLastForcedRequest = getCurrentModalityCount();
  }

  @Nullable
  private FocusCommand getLastEffectiveForcedRequest() {
    final FocusCommand request = SoftReference.dereference(myLastForcedRequest);
    return request != null && !request.isExpired() ? request : null;
  }

  boolean isUnforcedRequestAllowed() {
    if (getLastEffectiveForcedRequest() == null) return true;
    return myModalityStateForLastForcedRequest != getCurrentModalityCount();
  }

  public static FocusManagerImpl getInstance() {
    return (FocusManagerImpl)ApplicationManager.getApplication().getComponent(IdeFocusManager.class);
  }

  @Override
  public void dispose() {
    myForcedFocusRequestsAlarm.cancelAllRequests();
    myFocusedComponentAlarm.cancelAllRequests();
  }

  private class KeyProcessorContext implements KeyEventProcessor.Context {
    @Override
    @NotNull
    public List getQueue() {
      return myToDispatchOnDone;
    }

    @Override
    public void dispatch(@NotNull final List events) {
      doWhenFocusSettlesDown(new Runnable() {
        @Override
        public void run() {
          myToDispatchOnDone.addAll(events);
          restartIdleAlarm();
        }
      });
    }
  }

  @Override
  public void doWhenFocusSettlesDown(@NotNull ExpirableRunnable runnable) {
    doWhenFocusSettlesDown((Runnable)runnable);
  }

  @Override
  public void doWhenFocusSettlesDown(@NotNull final Runnable runnable) {
    UIUtil.invokeLaterIfNeeded(new Runnable() {
      @Override
      public void run() {
        if (isFlushingIdleRequests()) {
          myIdleRequests.add(runnable);
          return;
        }

        if (myRunContext != null) {
          flushRequest(runnable);
          return;
        }

        final boolean needsRestart = isIdleQueueEmpty();
        if (myIdleRequests.contains(runnable)) {
          myIdleRequests.remove(runnable);
          myIdleRequests.add(runnable);
        } else {
          myIdleRequests.add(runnable);
        }

        if (canFlushIdleRequests()) {
          flushIdleRequests();
        }
        else {
          if (needsRestart) {
            restartIdleAlarm();
          }
        }
      }
    });
  }

  private void restartIdleAlarm() {
    if (!ApplicationManager.getApplication().isActive()) return;
    myIdleAlarm.cancelAllRequests();
    myIdleAlarm.addRequest(new IdleRunnable(), Registry.intValue("actionSystem.focusIdleTimeout"));
  }

  private void flushIdleRequests() {
    int currentModalityCount = getCurrentModalityCount();
    try {
      incFlushingRequests(1, currentModalityCount);

      if (!isTypeaheadEnabled()) {
        myToDispatchOnDone.clear();
        myTypeAheadRequestors.clear();
      }

      if (!myToDispatchOnDone.isEmpty() && myTypeAheadRequestors.isEmpty()) {
        final KeyEvent[] events = myToDispatchOnDone.toArray(new KeyEvent[myToDispatchOnDone.size()]);

        IdeEventQueue.getInstance().getKeyEventDispatcher().resetState();

        for (int eachIndex = 0; eachIndex < events.length; eachIndex++) {
          if (!isFocusTransferReady()) {
            break;
          }

          KeyEvent each = events[eachIndex];

          Component owner = KeyboardFocusManager.getCurrentKeyboardFocusManager().getFocusOwner();
          if (owner == null) {
            owner = JOptionPane.getRootFrame();
          }

          boolean metaKey =
            each.getKeyCode() == KeyEvent.VK_ALT ||
            each.getKeyCode() == KeyEvent.VK_CONTROL ||
            each.getKeyCode() == KeyEvent.VK_SHIFT ||
            each.getKeyCode() == KeyEvent.VK_META;

          boolean toDispatch = false;
          if (!metaKey && (each.getID() == KeyEvent.KEY_RELEASED || each.getID() == KeyEvent.KEY_TYPED)) {
            for (int i = 0; i < eachIndex; i++) {
              final KeyEvent prev = events[i];
              if (prev == null) continue;

              if (prev.getID() == KeyEvent.KEY_PRESSED) {
                if (prev.getKeyCode() == each.getKeyCode() || prev.getKeyChar() == each.getKeyChar()) {
                  toDispatch = true;
                  events[i] = null;
                  break;
                }
              }
            }
          }
          else {
            toDispatch = true;
          }

          myToDispatchOnDone.remove(each);
          if (!toDispatch) {
            continue;
          }


          KeyEvent keyEvent = new KeyEvent(owner, each.getID(), each.getWhen(), each.getModifiersEx(), each.getKeyCode(), each.getKeyChar(),
                                           each.getKeyLocation());


          if (owner != null && SwingUtilities.getWindowAncestor(owner) != null) {
            IdeEventQueue.getInstance().dispatchEvent(keyEvent);
          }
          else {
            myQueue._dispatchEvent(keyEvent, true);
          }
        }

        if (myToDispatchOnDone.isEmpty() && myTypeAheadRequestors.isEmpty()) {
          myActivityMonitor.removeActivity(TYPEAHEAD);
        }
      }

      if (!isFocusBeingTransferred()) {
        boolean focusOk = getFocusOwner() != null;
        if (!focusOk && !myFlushWasDelayedToFixFocus) {
          IdeEventQueue.getInstance().fixStickyFocusedComponents(null);
          myFlushWasDelayedToFixFocus = true;
        }
        else if (!focusOk) {
          myFlushWasDelayedToFixFocus = false;
        }

        if (canFlushIdleRequests() && getFlushingIdleRequests() <= 1 && (focusOk || !myFlushWasDelayedToFixFocus)) {
          myFlushWasDelayedToFixFocus = false;
          flushNow();
        }
      }
    }
    finally {
      incFlushingRequests(-1, currentModalityCount);
      if (!isIdleQueueEmpty()) {
        restartIdleAlarm();
      }

      maybeRemoveFocusActivity();
    }
  }

  private boolean processFocusRevalidation() {
    ExpirableRunnable revalidator = myFocusRevalidator;
    myFocusRevalidator = null;
    
    if (revalidator != null && !revalidator.isExpired()) {
      revalidator.run();
      return true;
    }
    
    return false;
  }

  private void flushNow() {
    final Runnable[] all = myIdleRequests.toArray(new Runnable[myIdleRequests.size()]);
    myIdleRequests.clear();
    for (int i = 0; i < all.length; i++) {
      flushRequest(all[i]);
      if (isFocusBeingTransferred()) {
        for (int j = i + 1; j < all.length; j++) {
          myIdleRequests.add(all[j]);
        }
        break;
      }
    }
    
    maybeRemoveFocusActivity();
  }

  private static void flushRequest(Runnable each) {
    if (each == null) return;
    if (each instanceof Expirable) {
      if (!((Expirable)each).isExpired()) {
        each.run();
      }
    } else {
      each.run();
    }
  }

  public boolean isFocusTransferReady() {
    assertDispatchThread();

    if (myRunContext != null) return true;

    invalidateFocusRequestsQueue();

    if (!myFocusRequests.isEmpty()) return false;
    if (myQueue == null) return true;

    return !myQueue.isSuspendMode() && !myQueue.hasFocusEventsPending();
  }

  private void invalidateFocusRequestsQueue() {
    assertDispatchThread();
    UIUtil.invokeAndWaitIfNeeded(new Runnable() {
      @Override
      public void run() {
        if (myFocusRequests.isEmpty()) return;

        FocusCommand[] requests = myFocusRequests.toArray(new FocusCommand[myFocusRequests.size()]);
        boolean wasChanged = false;
        for (FocusCommand each : requests) {
          if (each.isExpired()) {
            resetCommand(each, true);
            wasChanged = true;
          }
        }

        if (wasChanged && myFocusRequests.isEmpty()) {
          restartIdleAlarm();
        }
      }
    });
  }

  private boolean isIdleQueueEmpty() {
    return isPendingKeyEventsRedispatched() && myIdleRequests.isEmpty();
  }

  private boolean isPendingKeyEventsRedispatched() {
    return myToDispatchOnDone.isEmpty();
  }

  @Override
  public boolean dispatch(@NotNull KeyEvent e) {
    if (!isTypeaheadEnabled()) return false;
    if (isFlushingIdleRequests()) return false;

    assertDispatchThread();

    if (!isFocusTransferReady() || !isPendingKeyEventsRedispatched() || !myTypeAheadRequestors.isEmpty()) {
      for (FocusCommand each : myFocusRequests) {
        final KeyEventProcessor processor = each.getProcessor();
        if (processor != null) {
          final Boolean result = processor.dispatch(e, myKeyProcessorContext);
          if (result != null) {
            if (result.booleanValue()) {
              myActivityMonitor.addActivity(TYPEAHEAD, ModalityState.any());
              return true;
            }
            return false;
          }
        }
      }

      myToDispatchOnDone.add(e);
      myActivityMonitor.addActivity(TYPEAHEAD, ModalityState.any());

      restartIdleAlarm();

      return true;
    }
    return false;
  }

  @Override
  public void setTypeaheadEnabled(boolean enabled) {
    myTypeaheadEnabled = enabled;
  }

  private boolean isTypeaheadEnabled() {
    return Registry.is("actionSystem.fixLostTyping") && myTypeaheadEnabled;
  }

  @Override
  public void typeAheadUntil(@NotNull ActionCallback callback) {
    if (!isTypeaheadEnabled()) return;

    final long currentTime = System.currentTimeMillis();
    final ActionCallback done;
    if (!Registry.is("type.ahead.logging.enabled")) {
      done = callback;
    }
    else {
      final String id = new Exception().getStackTrace()[2].getClassName();
      //LOG.setLevel(Level.ALL);
      final SimpleDateFormat dateFormat = new SimpleDateFormat("dd MMM yyyy HH:ss:SSS", Locale.US);
      LOG.info(dateFormat.format(System.currentTimeMillis()) + "\tStarted:  " + id);
      done = new ActionCallback();
      callback.doWhenDone(new Runnable() {
        @Override
        public void run() {
          done.setDone();
          LOG.info(dateFormat.format(System.currentTimeMillis()) + "\tDone:     " + id);
        }
      });
      callback.doWhenRejected(new Runnable() {
        @Override
        public void run() {
          done.setRejected();
          LOG.info(dateFormat.format(System.currentTimeMillis()) + "\tRejected: " + id);
        }
      });
    }
    assertDispatchThread();

    myTypeAheadRequestors.add(done);
    done.notify(new TimedOutCallback(Registry.intValue("actionSystem.commandProcessingTimeout"),
                                            "Typeahead request blocked",
                                            new Exception() {
                                              @Override
                                              public String getMessage() {
                                                return "Time: " + (System.currentTimeMillis() - currentTime);
                                              }
                                            },
                                            true).doWhenProcessed(new Runnable() {
      @Override
      public void run() {
        if (myTypeAheadRequestors.remove(done)) {
          restartIdleAlarm();
        }
      }
    }));
  }

  private boolean isFlushingIdleRequests() {
    return getFlushingIdleRequests() > 0;
  }

  private int getFlushingIdleRequests() {
    int currentModalityCount = getCurrentModalityCount();
    return myModalityCount2FlushCount.get(currentModalityCount);
  }

  private void incFlushingRequests(int delta, final int currentModalityCount) {
    if (myModalityCount2FlushCount.containsKey(currentModalityCount)) {
      myModalityCount2FlushCount.adjustValue(currentModalityCount, delta);
    }
    else {
      myModalityCount2FlushCount.put(currentModalityCount, delta);
    }
  }

  private int getCurrentModalityCount() {
    int modalityCount = 0;
    Window[] windows = Window.getWindows();
    for (Window each : windows) {
      if (!each.isShowing()) continue;

      if (each instanceof Dialog) {
        Dialog eachDialog = (Dialog)each;
        if (eachDialog.isModal()) {
          modalityCount++;
        }
        else if (each instanceof JDialog) {
          if (isModalContextPopup(((JDialog)each).getRootPane())) {
            modalityCount++;
          }
        }
      }
      else if (each instanceof JWindow) {
        JRootPane rootPane = ((JWindow)each).getRootPane();
        if (isModalContextPopup(rootPane)) {
          modalityCount++;
        }
      }
    }
    final int finalModalityCount = modalityCount;
    myModalityCount2FlushCount.retainEntries(new TIntIntProcedure() {
      @Override
      public boolean execute(int eachModalityCount, int flushCount) {
        return eachModalityCount <= finalModalityCount;
      }
    });

    return modalityCount;
  }

  private static boolean isModalContextPopup(@NotNull JRootPane rootPane) {
    final JBPopup popup = (JBPopup)rootPane.getClientProperty(JBPopup.KEY);
    return popup != null && popup.isModalContext();
  } 

  @NotNull
  @Override
  public Expirable getTimestamp(final boolean trackOnlyForcedCommands) {
    assertDispatchThread();

    return new Expirable() {
      long myOwnStamp = trackOnlyForcedCommands ? myForcedCmdTimestamp : myCmdTimestamp;

      @Override
      public boolean isExpired() {
        return myOwnStamp < (trackOnlyForcedCommands ? myForcedCmdTimestamp : myCmdTimestamp);
      }
    };
  }

  @NotNull
  @Override
  public FocusRequestor getFurtherRequestor() {
    assertDispatchThread();

    FurtherRequestor requestor = new FurtherRequestor(this, getTimestamp(true));
    myValidFurtherRequestors.add(requestor);
    revalidateFurtherRequestors();
    return requestor;
  }

  private void revalidateFurtherRequestors() {
    Iterator requestorIterator = myValidFurtherRequestors.iterator();
    while (requestorIterator.hasNext()) {
      FurtherRequestor each = requestorIterator.next();
      if (each.isExpired()) {
        requestorIterator.remove();
        Disposer.dispose(each);
      }
    }
  }
  
  @Override
  public void revalidateFocus(@NotNull final ExpirableRunnable runnable) {
    SwingUtilities.invokeLater(new Runnable() {
      @Override
      public void run() {
        myFocusRevalidator = runnable;
        restartIdleAlarm();
      }
    });
  }

  @Override
  public Component getFocusOwner() {
    assertDispatchThread();

    Component result = null;
    if (!ApplicationManager.getApplication().isActive()) {
      result = myLastFocusedAtDeactivation.get(getLastFocusedFrame());
    }
    else if (myRunContext != null) {
      result = (Component)myRunContext.getData(PlatformDataKeys.CONTEXT_COMPONENT.getName());
    }

    if (result == null) {
      result =  isFocusBeingTransferred() ? null : KeyboardFocusManager.getCurrentKeyboardFocusManager().getFocusOwner();
    }

    final boolean meaninglessOwner = UIUtil.isMeaninglessFocusOwner(result);
    if (result == null && !isFocusBeingTransferred() || meaninglessOwner) {
      final Component permOwner = KeyboardFocusManager.getCurrentKeyboardFocusManager().getPermanentFocusOwner();
      if (permOwner != null) {
        result = permOwner;
      }
      
      if (UIUtil.isMeaninglessFocusOwner(result)) {
        result = KeyboardFocusManager.getCurrentKeyboardFocusManager().getActiveWindow();
      }
    }

    return result;
  }

  @Override
  public void runOnOwnContext(@NotNull DataContext context, @NotNull Runnable runnable) {
    assertDispatchThread();

    myRunContext = context;
    try {
      runnable.run();
    }
    finally {
      myRunContext = null;
    }
  }

  @Override
  public Component getLastFocusedFor(IdeFrame frame) {
    assertDispatchThread();

    return myLastFocused.get(frame);
  }

  public void setLastFocusedAtDeactivation(@NotNull IdeFrame frame, @NotNull Component c) {
    myLastFocusedAtDeactivation.put(frame, c);
  }

  @Override
  public void toFront(JComponent c) {
    assertDispatchThread();

    if (c == null) return;

    final Window window = UIUtil.getParentOfType(Window.class, c);
    if (window != null && window.isShowing()) {
      doWhenFocusSettlesDown(new Runnable() {
        @Override
        public void run() {
          if (ApplicationManager.getApplication().isActive()) {
            if (window instanceof JFrame && ((JFrame)window).getState() == Frame.ICONIFIED) {
              ((JFrame)window).setState(Frame.NORMAL);
            } else {
              window.toFront();
            }
          }
        }
      });
    }
  }

  private static class FurtherRequestor implements FocusRequestor {
    private final IdeFocusManager myManager;
    private final Expirable myExpirable;
    private Throwable myAllocation;
    private boolean myDisposed;

    private FurtherRequestor(@NotNull IdeFocusManager manager, @NotNull Expirable expirable) {
      myManager = manager;
      myExpirable = expirable;
      if (Registry.is("ide.debugMode")) {
        myAllocation = new Exception();
      }
    }

    @NotNull
    @Override
    public ActionCallback requestFocus(@NotNull Component c, boolean forced) {
      final ActionCallback result = isExpired() ? new ActionCallback.Rejected() : myManager.requestFocus(c, forced);
      result.doWhenProcessed(new Runnable() {
        @Override
        public void run() {
          Disposer.dispose(FurtherRequestor.this);
        }
      });
      return result;
    }

    private boolean isExpired() {
      return myExpirable.isExpired() || myDisposed;
    }

    @NotNull
    @Override
    public ActionCallback requestFocus(@NotNull FocusCommand command, boolean forced) {
      return isExpired() ? new ActionCallback.Rejected() : myManager.requestFocus(command, forced);
    }

    @Override
    public void dispose() {
      myDisposed = true;
    }
  }


  class EdtAlarm {
    private final Set myRequests = new HashSet();
    
    public void cancelAllRequests() {
      for (EdtRunnable each : myRequests) {
        each.expire();
      }
      myRequests.clear();
    }

    public void addRequest(@NotNull EdtRunnable runnable, int delay) {
      myRequests.add(runnable);
      myTimer.setUp(runnable, delay);
    }
  }

  private void forceFinishFocusSettleDown(@NotNull FocusCommand cmd, @NotNull ActionCallback cmdCallback) {
    rejectCommand(cmd, cmdCallback);
  }


  private void rejectCommand(@NotNull FocusCommand cmd, @NotNull ActionCallback callback) {
    resetCommand(cmd, true);
    resetUnforcedCommand(cmd);

    callback.setRejected();
  }

  private class AppListener implements ApplicationActivationListener {
    @Override
    public void applicationDeactivated(IdeFrame ideFrame) {
      final Component owner = KeyboardFocusManager.getCurrentKeyboardFocusManager().getFocusOwner();
      Component parent = UIUtil.findUltimateParent(owner);

      if (parent == ideFrame) {
        myLastFocusedAtDeactivation.put(ideFrame, owner);
      }
    }

    @Override
    public void applicationActivated(final IdeFrame ideFrame) {
      final FocusCommand cmd = myFocusCommandOnAppActivation;
      ActionCallback callback = myCallbackOnActivation;
      myFocusCommandOnAppActivation = null;
      myCallbackOnActivation = null;

      if (cmd != null) {
        requestFocus(cmd, true).notify(callback);
      } else {
        focusLastFocusedComponent(ideFrame);
      }
    }

    private void focusLastFocusedComponent(IdeFrame ideFrame) {
      final KeyboardFocusManager mgr = KeyboardFocusManager.getCurrentKeyboardFocusManager();
      if (mgr.getFocusOwner() == null) {
        Component c = getComponent(myLastFocusedAtDeactivation, ideFrame);
        if (c == null || !c.isShowing()) {
          c = getComponent(myLastFocusedAtDeactivation, ideFrame);
        }

        final boolean mouseEventAhead = IdeEventQueue.isMouseEventAhead(null);
        if (c != null && c.isShowing() && !mouseEventAhead) {
          final LayoutFocusTraversalPolicyExt policy = LayoutFocusTraversalPolicyExt.findWindowPolicy(c);
          if (policy != null) {
            policy.setNoDefaultComponent(true, FocusManagerImpl.this);
          }
          requestFocus(c, false).doWhenProcessed(new Runnable() {
            @Override
            public void run() {
              if (policy != null) {
                policy.setNoDefaultComponent(false, FocusManagerImpl.this);
              }
            }
          });
        }
      }

      myLastFocusedAtDeactivation.remove(ideFrame);
    }
  }

  @Nullable
  private static Component getComponent(@NotNull Map map, IdeFrame frame) {
    return map.get(frame);
  }

  @Override
  public JComponent getFocusTargetFor(@NotNull JComponent comp) {
    return IdeFocusTraversalPolicy.getPreferredFocusedComponent(comp);
  }

  @Override
  public Component getFocusedDescendantFor(Component comp) {
    final Component focused = getFocusOwner();
    if (focused == null) return null;

    if (focused == comp || SwingUtilities.isDescendingFrom(focused, comp)) return focused;

    List popups = FocusTrackback.getChildPopups(comp);
    for (JBPopup each : popups) {
      if (each.isFocused()) return focused;
    }

    return null;
  }

  @Override
  public boolean isFocusBeingTransferred() {
    return !isFocusTransferReady();
  }

  @NotNull
  @Override
  public ActionCallback requestDefaultFocus(boolean forced) {
    Component toFocus = null;
    if (myLastFocusedFrame != null) {
      toFocus = myLastFocused.get(myLastFocusedFrame);
      if (toFocus == null || !toFocus.isShowing()) {
        toFocus = getFocusTargetFor(myLastFocusedFrame.getComponent());
      }
    }
    else {
      Window[] windows = Window.getWindows();
      for (Window each : windows) {
        if (each.isActive()) {
          if (each instanceof JFrame) {
            toFocus = getFocusTargetFor(((JFrame)each).getRootPane());
            break;
          } else if (each instanceof JDialog) {
            toFocus = getFocusTargetFor(((JDialog)each).getRootPane());
            break;
          } else if (each instanceof JWindow) {
            toFocus = getFocusTargetFor(((JWindow)each).getRootPane());
            break;
          }          
        }
      }
    } 
    
    if (toFocus != null) {
      return requestFocus(new FocusCommand.ByComponent(toFocus, new Exception()).setToInvalidateRequestors(false), forced);
    }
    
    
    return new ActionCallback.Done();
  }

  @Override
  public boolean isFocusTransferEnabled() {
    if (Registry.is("focus.fix.lost.cursor")) return true;
    return myApp.isActive() || !Registry.is("actionSystem.suspendFocusTransferIfApplicationInactive");
  }

  private static void assertDispatchThread() {
    if (Registry.is("actionSystem.assertFocusAccessFromEdt")) {
      ApplicationManager.getApplication().assertIsDispatchThread();
    }
  }

  private class SetLastEffectiveRunnable extends EdtRunnable {
    @Override
    public void runEdt() {
      setLastEffectiveForcedRequest(null);
    }
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy