| /* |
| * Copyright 2000-2011 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.editor.impl; |
| |
| import com.intellij.openapi.editor.ex.util.EditorUtil; |
| import com.intellij.util.Alarm; |
| import gnu.trove.TLongArrayList; |
| import org.jetbrains.annotations.NotNull; |
| import org.jetbrains.annotations.Nullable; |
| |
| import javax.swing.*; |
| import java.awt.*; |
| |
| /** |
| * This class is aimed to help {@link EditorImpl the editor} when user extensively modifies the longest line |
| * at the document (e.g. is typing at its end). |
| * <p/> |
| * The problem is that the longest line's width is a {@link JComponent#getPreferredSize() preferred size's width} as well. |
| * So, every time width of the longest line is changed, editor's preferred size is changed too and that triggers the whole |
| * component repaint. |
| * <p/> |
| * This component comes into the play here - it's assumed that editor notifies it every time preferred size is changed and |
| * receives instructions for the further actions. For example, we can reserve additional space if we see that preferred size |
| * is permanently increasing (the user is typing at the end of the longest line) etc. See method javadocs for more details. |
| * <p/> |
| * Not thread-safe. |
| * |
| * @author Denis Zhdanov |
| * @since 6/17/11 2:45 PM |
| */ |
| public class EditorSizeAdjustmentStrategy { |
| |
| /** Amount of time (in milliseconds) to keep information about preferred size change. */ |
| private static final long TIMING_TTL_MILLIS = 10000L; |
| |
| /** Constant that indicates minimum number of preferred size changes per target amount of time that is considered to be frequent. */ |
| private static final int FREQUENT_SIZE_CHANGES_NUMBER = 10; |
| |
| /** Default number of columns to reserve during frequent typing at the end of the longest document line. */ |
| private static final int DEFAULT_RESERVE_COLUMNS_NUMBER = 4; |
| |
| private final Alarm myAlarm = new Alarm(); |
| private final TLongArrayList myTimings = new TLongArrayList(); |
| |
| private int myReserveColumns = DEFAULT_RESERVE_COLUMNS_NUMBER; |
| private boolean mySkip; |
| |
| /** |
| * Asks to adjust new preferred size appliance if necessary. |
| * |
| * @param newPreferredSize newly calculated preferred size that differs from the old preferred size |
| * @param oldPreferredSize old preferred size (if any) |
| * @param editor target editor |
| * @return preferred size to use (given 'new preferred size' may be adjusted); <code>null</code> if given |
| * new preferred size should not be applied |
| */ |
| @Nullable |
| public Dimension adjust(@NotNull Dimension newPreferredSize, @Nullable Dimension oldPreferredSize, @NotNull EditorImpl editor) { |
| if (oldPreferredSize == null || mySkip) { |
| return newPreferredSize; |
| } |
| // Process only width change. |
| if (newPreferredSize.height != oldPreferredSize.height) { |
| return newPreferredSize; |
| } |
| |
| stripTimings(); |
| myTimings.add(System.currentTimeMillis()); |
| if (myTimings.size() < FREQUENT_SIZE_CHANGES_NUMBER) { |
| return newPreferredSize; |
| } |
| |
| boolean increaseWidth = newPreferredSize.width > oldPreferredSize.width; |
| Dimension result; |
| if (increaseWidth) { |
| final int spaceWidth = EditorUtil.getSpaceWidth(Font.PLAIN, editor); |
| newPreferredSize.width += myReserveColumns * spaceWidth; |
| myReserveColumns += 3; |
| result = newPreferredSize; |
| } |
| else { |
| // Don't reduce preferred size on frequent reduce of the longest document line. |
| result = oldPreferredSize; |
| } |
| |
| scheduleSizeUpdate(editor); |
| return result; |
| } |
| |
| /** |
| * Removes old timings. |
| */ |
| private void stripTimings() { |
| long limit = System.currentTimeMillis() - TIMING_TTL_MILLIS; |
| int endIndex = 0; |
| for (; endIndex < myTimings.size(); endIndex++) { |
| if (myTimings.get(endIndex) > limit) { |
| break; |
| } |
| } |
| if (endIndex > 0) { |
| myTimings.remove(0, endIndex); |
| } |
| } |
| |
| private void scheduleSizeUpdate(@NotNull EditorImpl editor) { |
| myAlarm.cancelAllRequests(); |
| myAlarm.addRequest(new UpdateSizeTask(editor), 1000); |
| } |
| |
| private class UpdateSizeTask implements Runnable { |
| |
| private final EditorImpl myEditor; |
| |
| UpdateSizeTask(@NotNull EditorImpl editor) { |
| myEditor = editor; |
| } |
| |
| @Override |
| public void run() { |
| mySkip = true; |
| myReserveColumns = DEFAULT_RESERVE_COLUMNS_NUMBER; |
| myTimings.clear(); |
| try { |
| myEditor.validateSize(); |
| } |
| finally { |
| mySkip = false; |
| } |
| } |
| } |
| } |