/* * Copyright 2000-2016 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 */ 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 myInsideValidation; /** * 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) */ @NotNull Dimension adjust(@NotNull Dimension newPreferredSize, @Nullable Dimension oldPreferredSize, @NotNull EditorImpl editor) { if (oldPreferredSize == null || myInsideValidation) { 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; } void cancelAllRequests() { myAlarm.cancelAllRequests(); } /** * 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() { myInsideValidation = true; myReserveColumns = DEFAULT_RESERVE_COLUMNS_NUMBER; myTimings.clear(); try { myEditor.validateSize(); } finally { myInsideValidation = false; } } } }