| /* |
| * Copyright 2000-2013 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.ide.navigationToolbar; |
| |
| import com.intellij.ide.DataManager; |
| import com.intellij.ide.IdeEventQueue; |
| import com.intellij.openapi.actionSystem.CommonDataKeys; |
| import com.intellij.openapi.actionSystem.DataContext; |
| import com.intellij.openapi.project.Project; |
| import com.intellij.openapi.util.ActionCallback; |
| import com.intellij.openapi.util.registry.Registry; |
| import com.intellij.openapi.wm.IdeFocusManager; |
| import com.intellij.openapi.wm.IdeFrame; |
| import com.intellij.ui.LightweightHint; |
| import com.intellij.ui.awt.RelativePoint; |
| import com.intellij.util.Alarm; |
| import com.intellij.util.Consumer; |
| import com.intellij.util.ui.UIUtil; |
| import com.intellij.util.ui.update.MergingUpdateQueue; |
| import com.intellij.util.ui.update.Update; |
| import org.jetbrains.annotations.Nullable; |
| |
| import javax.swing.*; |
| import java.awt.*; |
| import java.util.concurrent.atomic.AtomicBoolean; |
| |
| /** |
| * @author Konstantin Bulenkov |
| */ |
| public class NavBarUpdateQueue extends MergingUpdateQueue { |
| private AtomicBoolean myModelUpdating = new AtomicBoolean(Boolean.FALSE); |
| private Alarm myUserActivityAlarm = new Alarm(this); |
| private Runnable myRunWhenListRebuilt; |
| private Runnable myUserActivityAlarmRunnable = new Runnable() { |
| @Override |
| public void run() { |
| processUserActivity(); |
| } |
| }; |
| |
| private NavBarPanel myPanel; |
| |
| public NavBarUpdateQueue(NavBarPanel panel) { |
| super("NavBar", Registry.intValue("navBar.updateMergeTime"), true, MergingUpdateQueue.ANY_COMPONENT, panel); |
| myPanel = panel; |
| setTrackUiActivity(true); |
| IdeEventQueue.getInstance().addActivityListener(new Runnable() { |
| @Override |
| public void run() { |
| restartRebuild(); |
| } |
| }, panel); |
| } |
| |
| private void requestModelUpdate(@Nullable final DataContext context, final @Nullable Object object, boolean requeue) { |
| if (myModelUpdating.getAndSet(true) && !requeue) return; |
| |
| cancelAllUpdates(); |
| |
| queue(new AfterModelUpdate(ID.MODEL) { |
| @Override |
| public void run() { |
| if (context != null || object != null) { |
| requestModelUpdateFromContextOrObject(context, object); |
| } else { |
| DataManager.getInstance().getDataContextFromFocus().doWhenDone(new Consumer<DataContext>() { |
| @Override |
| public void consume(DataContext dataContext) { |
| requestModelUpdateFromContextOrObject(dataContext, null); |
| } |
| }); |
| } |
| } |
| |
| @Override |
| public void setRejected() { |
| super.setRejected(); |
| myModelUpdating.set(false); |
| } |
| }); |
| } |
| |
| private void requestModelUpdateFromContextOrObject(DataContext dataContext, Object object) { |
| final NavBarModel model = myPanel.getModel(); |
| if (dataContext != null) { |
| if (CommonDataKeys.PROJECT.getData(dataContext) != myPanel.getProject() || myPanel.isNodePopupShowing()) { |
| requestModelUpdate(null, myPanel.getContextObject(), true); |
| return; |
| } |
| final Window window = SwingUtilities.getWindowAncestor(myPanel); |
| if (window != null && !window.isFocused()) { |
| model.updateModel(DataManager.getInstance().getDataContext(myPanel)); |
| } else { |
| model.updateModel(dataContext); |
| } |
| } else { |
| model.updateModel(object); |
| } |
| |
| queueRebuildUi(); |
| |
| myModelUpdating.set(false); |
| } |
| |
| void restartRebuild() { |
| myUserActivityAlarm.cancelAllRequests(); |
| if (!myUserActivityAlarm.isDisposed()) { |
| myUserActivityAlarm.addRequest(myUserActivityAlarmRunnable, Registry.intValue("navBar.userActivityMergeTime")); |
| } |
| } |
| |
| private void processUserActivity() { |
| if (myPanel == null || !myPanel.isShowing()) { |
| return; |
| } |
| |
| final Project project = myPanel.getProject(); |
| IdeFocusManager.getInstance(project).doWhenFocusSettlesDown(new Runnable() { |
| @Override |
| public void run() { |
| Window wnd = SwingUtilities.windowForComponent(myPanel); |
| if (wnd == null) return; |
| |
| Component focus = null; |
| |
| if (!wnd.isActive()) { |
| IdeFrame frame = UIUtil.getParentOfType(IdeFrame.class, myPanel); |
| if (frame != null) { |
| focus = IdeFocusManager.getInstance(project).getLastFocusedFor(frame); |
| } |
| } |
| else { |
| final Window window = KeyboardFocusManager.getCurrentKeyboardFocusManager().getFocusedWindow(); |
| if (window instanceof Dialog) { |
| final Dialog dialog = (Dialog)window; |
| if (dialog.isModal() && !SwingUtilities.isDescendingFrom(myPanel, dialog)) { |
| return; |
| } |
| } |
| } |
| |
| if (focus != null && focus.isShowing()) { |
| if (!myPanel.hasFocus() && !myPanel.isNodePopupShowing()) { |
| requestModelUpdate(DataManager.getInstance().getDataContext(focus), null, false); |
| } |
| } |
| else if (wnd.isActive()) { |
| requestModelUpdate(null, myPanel.getContextObject(), false); |
| } |
| } |
| }); |
| } |
| |
| public void queueModelUpdate(DataContext context) { |
| requestModelUpdate(context, null, false); |
| } |
| |
| public void queueModelUpdateFromFocus() { |
| queueModelUpdateFromFocus(false); |
| } |
| |
| public void queueModelUpdateFromFocus(boolean requeue) { |
| requestModelUpdate(null, myPanel.getContextObject(), requeue); |
| } |
| |
| public void queueModelUpdateForObject(Object object) { |
| requestModelUpdate(null, object, false); |
| } |
| |
| |
| public void queueRebuildUi() { |
| queue(new AfterModelUpdate(ID.UI) { |
| @Override |
| protected void after() { |
| rebuildUi(); |
| } |
| }); |
| queueRevalidate(null); |
| } |
| |
| public void rebuildUi() { |
| if (!myPanel.isRebuildUiNeeded()) return; |
| |
| myPanel.clearItems(); |
| for (int index = 0; index < myPanel.getModel().size(); index++) { |
| final Object object = myPanel.getModel().get(index); |
| final NavBarItem label = new NavBarItem(myPanel, object, index, null); |
| |
| myPanel.installActions(index, label); |
| myPanel.addItem(label); |
| |
| } |
| |
| rebuildComponent(); |
| |
| if (myRunWhenListRebuilt != null) { |
| Runnable r = myRunWhenListRebuilt; |
| myRunWhenListRebuilt = null; |
| r.run(); |
| } |
| } |
| |
| |
| private void rebuildComponent() { |
| myPanel.removeAll(); |
| |
| for (NavBarItem item : myPanel.getItems()) { |
| myPanel.add(item); |
| } |
| |
| myPanel.revalidate(); |
| myPanel.repaint(); |
| |
| queueAfterAll(new Runnable() { |
| @Override |
| public void run() { |
| myPanel.scrollSelectionToVisible(); |
| } |
| }, ID.SCROLL_TO_VISIBLE); |
| } |
| |
| private void queueRevalidate(@Nullable final Runnable after) { |
| queue(new AfterModelUpdate(ID.REVALIDATE) { |
| @Override |
| protected void after() { |
| final LightweightHint hint = myPanel.getHint(); |
| if (hint != null) { |
| myPanel.getHintContainerShowPoint().doWhenDone(new Consumer<RelativePoint>() { |
| @Override |
| public void consume(final RelativePoint relativePoint) { |
| hint.setSize(myPanel.getPreferredSize()); |
| hint.setLocation(relativePoint); |
| if (after != null) { |
| after.run(); |
| } |
| } |
| }); |
| } |
| else { |
| if (after != null) { |
| after.run(); |
| } |
| } |
| } |
| }); |
| } |
| |
| void queueSelect(final Runnable runnable) { |
| queue(new AfterModelUpdate(NavBarUpdateQueue.ID.SELECT) { |
| @Override |
| protected void after() { |
| runnable.run(); |
| } |
| }); |
| } |
| |
| void queueAfterAll(final Runnable runnable, ID id) { |
| queue(new AfterModelUpdate(id) { |
| @Override |
| protected void after() { |
| if (runnable != null) { |
| runnable.run(); |
| } |
| } |
| }); |
| } |
| |
| @Override |
| public void dispose() { |
| super.dispose(); |
| } |
| |
| public void queueTypeAheadDone(final ActionCallback done) { |
| queue(new AfterModelUpdate(ID.TYPE_AHEAD_FINISHED) { |
| @Override |
| protected void after() { |
| done.setDone(); |
| } |
| }); |
| } |
| |
| private abstract class AfterModelUpdate extends Update { |
| private AfterModelUpdate(ID id) { |
| super(id.name(), id.getPriority()); |
| } |
| |
| @Override |
| public void run() { |
| if (myModelUpdating.get()) { |
| queue(this); |
| } else { |
| after(); |
| } |
| } |
| protected void after() {} |
| } |
| |
| public enum ID { |
| MODEL(0), |
| UI(1), |
| REVALIDATE(2), |
| SELECT(3), |
| SCROLL_TO_VISIBLE(4), |
| SHOW_HINT(4), |
| REQUEST_FOCUS(4), |
| NAVIGATE_INSIDE(4), |
| TYPE_AHEAD_FINISHED(5); |
| |
| private final int myPriority; |
| |
| ID(int priority) { |
| myPriority = priority; |
| } |
| |
| public int getPriority() { |
| return myPriority; |
| } |
| } |
| } |