blob: 7e24e92c5cf16a36bc69389c0fd715d9f968608b [file] [log] [blame]
/*
* Copyright 2000-2009 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.diff.impl.incrementalMerge.ui;
import com.intellij.openapi.Disposable;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.diff.impl.DiffUtil;
import com.intellij.openapi.diff.impl.highlighting.FragmentSide;
import com.intellij.openapi.diff.impl.splitter.DividerPolygon;
import com.intellij.openapi.diff.impl.util.DiffDivider;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.editor.EditorFactory;
import com.intellij.openapi.editor.ex.EditorEx;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.ui.ButtonlessScrollBarUI;
import com.intellij.util.ui.UIUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.swing.*;
import javax.swing.plaf.ScrollBarUI;
import java.awt.*;
import java.util.*;
import java.util.List;
/**
* The container for an {@link Editor}, which is added then to {@link com.intellij.openapi.diff.impl.util.ThreePanels}.
*/
public class EditorPlace extends JComponent implements Disposable, EditorEx.RepaintCallback {
private static final Logger LOG = Logger.getInstance(EditorPlace.class);
@NotNull private final MergePanel2.DiffEditorState myState;
@NotNull private final MergePanelColumn myColumn;
@NotNull private final MergePanel2 myMergePanel;
@NotNull private final List<EditorListener> myListeners = ContainerUtil.createLockFreeCopyOnWriteList();
@Nullable private EditorEx myEditor;
public EditorPlace(@NotNull MergePanel2.DiffEditorState state, @NotNull MergePanelColumn column, @NotNull MergePanel2 mergePanel) {
myState = state;
myColumn = column;
myMergePanel = mergePanel;
setLayout(new BorderLayout());
}
@Override
public void paint(Graphics g) {
super.paint(g);
paintThis(g);
}
public void call(Graphics g) {
repaintScrollbar();
}
private void repaintScrollbar() {
if (myEditor == null || myColumn != MergePanelColumn.BASE) {
return; // we draw above the scrollbar only in the central column
}
Component editorComponent = myEditor.getComponent();
JScrollBar scrollBar = myEditor.getScrollPane().getVerticalScrollBar();
repaint(editorComponent.getWidth() - scrollBar.getWidth(), 0, scrollBar.getWidth(), scrollBar.getHeight());
}
private void paintThis(Graphics g) {
if (myEditor != null) {
ArrayList<DividerPolygon> polygons = DividerPolygon.createVisiblePolygons(myMergePanel.getSecondEditingSide(), FragmentSide.SIDE1,
DiffDivider.MERGE_DIVIDER_POLYGONS_OFFSET);
for (DividerPolygon polygon : polygons) {
int startY = polygon.getTopLeftY();
int endY = polygon.getBottomLeftY();
int height = endY - startY;
if (height == 0) { // draw at least a one-pixel line (e.g. for insertion or deletion), as it is done in highlighters
height = 1;
}
drawPolygonAboveScrollBar((Graphics2D)g, startY, height, polygon.getColor(), polygon.isApplied());
}
}
}
private void drawPolygonAboveScrollBar(@NotNull Graphics2D g, int startY, int height, @NotNull Color color, boolean applied) {
// painting only above the central scrollbar, because painting on edge scrollbars is not needed, and there are error stripes
if (myColumn != MergePanelColumn.BASE) {
return;
}
g.setColor(color);
JScrollBar scrollBar = myEditor.getScrollPane().getVerticalScrollBar();
int startX = scrollBar.getX();
int endX = startX + scrollBar.getWidth() - 1;
Rectangle thumb = calcThumbBounds(scrollBar);
int endY = startY + height;
if (!applied) {
if (height > 2) {
fillRectAboveScrollBar(g, startX, startY, scrollBar.getWidth(), height, thumb);
Color framingColor = DiffUtil.getFramingColor(color);
if (outsideBounds(startY, thumb)) {
UIUtil.drawLine(g, startX, startY, endX, startY, null, framingColor);
}
if (outsideBounds(endY, thumb)) {
UIUtil.drawLine(g, startX, endY, endX, endY, null, framingColor);
}
}
else {
if (outsideBounds(startY, thumb)) {
DiffUtil.drawDoubleShadowedLine(g, startX, endX, startY, color);
}
}
}
else {
if (outsideBounds(startY, thumb)) {
UIUtil.drawBoldDottedLine(g, startX, endX, startY, null, color, false);
}
if (outsideBounds(endY, thumb)) {
UIUtil.drawBoldDottedLine(g, startX, endX, endY, null, color, false);
}
}
}
private static void fillRectAboveScrollBar(Graphics2D g, int startX, int startY, int width, int height, Rectangle thumb) {
int endY = startY + height;
int thumbEndY = thumb == null ? 0 : thumb.y + thumb.height; // it's for further readability (could have a 2-level ifs for the variable)
if (thumb == null) {
g.fillRect(startX, startY, width, height);
}
else if (outsideBounds(startY, thumb) && !outsideBounds(endY, thumb)) {
g.fillRect(startX, startY, width, thumb.y - startY);
}
else if (!outsideBounds(startY, thumb) && outsideBounds(endY, thumb)) {
g.fillRect(startX, thumbEndY, width, endY - thumbEndY);
}
else if (startY < thumb.y && endY > thumbEndY) { // surrounding the thumb
g.fillRect(startX, startY, width, thumb.y - startY);
g.fillRect(startX, thumbEndY, width, endY - thumbEndY);
}
else if (outsideBounds(startY, thumb) && outsideBounds(endY, thumb)) { // outside without intersection
g.fillRect(startX, startY, width, height);
}
}
private static boolean outsideBounds(int y, @Nullable Rectangle rectangle) {
if (rectangle == null) {
return true;
}
return y < rectangle.y || y > rectangle.y + rectangle.height;
}
@Nullable
private static Rectangle calcThumbBounds(JScrollBar scrollBar) {
ScrollBarUI scrollBarUI = scrollBar.getUI();
if (scrollBarUI instanceof ButtonlessScrollBarUI) {
return ((ButtonlessScrollBarUI)scrollBarUI).getThumbBounds();
}
return null;
}
public void addNotify() {
if (myEditor != null) {
super.addNotify();
return;
}
createEditor();
super.addNotify();
revalidate();
}
private void createEditor() {
LOG.assertTrue(myEditor == null);
myEditor = myState.createEditor();
if (myEditor == null) return;
add(myEditor.getComponent(), BorderLayout.CENTER);
myEditor.registerScrollBarRepaintCallback(this);
repaint();
fireEditorCreated();
}
public void addListener(EditorListener listener) {
myListeners.add(listener);
}
private void fireEditorCreated() {
for (EditorListener listener : myListeners) {
listener.onEditorCreated(this);
}
}
private void fireEditorReleased(Editor releasedEditor) {
for (EditorListener listener : myListeners) {
listener.onEditorReleased(releasedEditor);
}
}
public void removeNotify() {
removeEditor();
super.removeNotify();
}
private void removeEditor() {
if (myEditor != null) {
myEditor.registerScrollBarRepaintCallback(null);
Editor releasedEditor = myEditor;
remove(myEditor.getComponent());
getEditorFactory().releaseEditor(myEditor);
myEditor = null;
fireEditorReleased(releasedEditor);
}
}
@Nullable
public Editor getEditor() {
return myEditor;
}
public void setDocument(@Nullable Document document) {
if (document == getDocument()) return;
removeEditor();
myState.setDocument(document);
createEditor();
}
private Document getDocument() {
return myState.getDocument();
}
@NotNull
public MergePanel2.DiffEditorState getState() {
return myState;
}
public interface EditorListener {
void onEditorCreated(EditorPlace place);
void onEditorReleased(Editor releasedEditor);
}
private static EditorFactory getEditorFactory() {
return EditorFactory.getInstance();
}
@Nullable
public JComponent getContentComponent() {
return myEditor == null ? null : myEditor.getContentComponent();
}
public void dispose() {
removeEditor();
}
}