blob: 6bd2e1aa52bfd8e4f3b693b28d7ae9c4c0ba6606 [file] [log] [blame]
/*
* 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.ui.tabs.impl.singleRow;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.ui.tabs.JBTabsPosition;
import com.intellij.ui.tabs.TabInfo;
import com.intellij.ui.tabs.TabsUtil;
import com.intellij.ui.tabs.impl.*;
import org.jetbrains.annotations.Nullable;
import javax.swing.*;
import java.awt.*;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
public class SingleRowLayout extends TabLayout {
final JBTabsImpl myTabs;
public SingleRowPassInfo myLastSingRowLayout;
private final SingleRowLayoutStrategy myTop;
private final SingleRowLayoutStrategy myLeft;
private final SingleRowLayoutStrategy myBottom;
private final SingleRowLayoutStrategy myRight;
public final MoreTabsIcon myMoreIcon = new MoreTabsIcon() {
@Nullable
protected Rectangle getIconRec() {
return myLastSingRowLayout != null ? myLastSingRowLayout.moreRect : null;
}
@Override
protected int getIconY(Rectangle iconRec) {
final int shift;
switch (myTabs.getTabsPosition()) {
case bottom: shift = TabsUtil.ACTIVE_TAB_UNDERLINE_HEIGHT; break;
case top: shift = -(TabsUtil.ACTIVE_TAB_UNDERLINE_HEIGHT / 2); break;
default: shift = 0;
}
return super.getIconY(iconRec) + shift;
}
};
public JPopupMenu myMorePopup;
public final GhostComponent myLeftGhost = new GhostComponent(RowDropPolicy.first, RowDropPolicy.first);
public final GhostComponent myRightGhost = new GhostComponent(RowDropPolicy.last, RowDropPolicy.first);
private enum RowDropPolicy {
first, last
}
private RowDropPolicy myRowDropPolicy = RowDropPolicy.first;
@Override
public boolean isSideComponentOnTabs() {
return getStrategy().isSideComponentOnTabs();
}
@Override
public ShapeTransform createShapeTransform(Rectangle labelRec) {
return getStrategy().createShapeTransform(labelRec);
}
@Override
public boolean isDragOut(TabLabel tabLabel, int deltaX, int deltaY) {
return getStrategy().isDragOut(tabLabel, deltaX, deltaY);
}
public SingleRowLayout(final JBTabsImpl tabs) {
myTabs = tabs;
myTop = new SingleRowLayoutStrategy.Top(this);
myLeft = new SingleRowLayoutStrategy.Left(this);
myBottom = new SingleRowLayoutStrategy.Bottom(this);
myRight = new SingleRowLayoutStrategy.Right(this);
}
SingleRowLayoutStrategy getStrategy() {
switch (myTabs.getPresentation().getTabsPosition()) {
case top:
return myTop;
case left:
return myLeft;
case bottom:
return myBottom;
case right:
return myRight;
}
return null;
}
protected boolean checkLayoutLabels(SingleRowPassInfo data) {
boolean layoutLabels = true;
if (!myTabs.myForcedRelayout &&
myLastSingRowLayout != null &&
myLastSingRowLayout.contentCount == myTabs.getTabCount() &&
myLastSingRowLayout.layoutSize.equals(myTabs.getSize()) &&
myLastSingRowLayout.scrollOffset == getScrollOffset()) {
for (TabInfo each : data.myVisibleInfos) {
final TabLabel eachLabel = myTabs.myInfo2Label.get(each);
if (!eachLabel.isValid()) {
layoutLabels = true;
break;
}
if (myTabs.getSelectedInfo() == each) {
if (eachLabel.getBounds().width != 0) {
layoutLabels = false;
}
}
}
}
return layoutLabels;
}
int getScrollOffset() {
return 0;
}
public void scroll(int units) {
}
public int getScrollUnitIncrement() {
return 0;
}
public void scrollSelectionInView() {
}
public LayoutPassInfo layoutSingleRow(List<TabInfo> visibleInfos) {
if (myTabs.isAlphabeticalMode()) {
Collections.sort(visibleInfos, new Comparator<TabInfo>() {
@Override
public int compare(TabInfo o1, TabInfo o2) {
return StringUtil.naturalCompare(o1.getText(), o2.getText());
}
});
}
SingleRowPassInfo data = new SingleRowPassInfo(this, visibleInfos);
final boolean layoutLabels = checkLayoutLabels(data);
if (!layoutLabels) {
data = myLastSingRowLayout;
}
final TabInfo selected = myTabs.getSelectedInfo();
prepareLayoutPassInfo(data, selected);
myTabs.resetLayout(layoutLabels || myTabs.isHideTabs());
if (layoutLabels && !myTabs.isHideTabs()) {
data.position = getStrategy().getStartPosition(data) - getScrollOffset();
recomputeToLayout(data);
layoutLabelsAndGhosts(data);
layoutMoreButton(data);
}
if (selected != null) {
data.comp = selected.getComponent();
getStrategy().layoutComp(data);
}
updateMoreIconVisibility(data);
data.tabRectangle = new Rectangle();
if (data.toLayout.size() > 0) {
final TabLabel firstLabel = myTabs.myInfo2Label.get(data.toLayout.get(0));
final TabLabel lastLabel = findLastVisibleLabel(data);
if (firstLabel != null && lastLabel != null) {
data.tabRectangle.x = firstLabel.getBounds().x;
data.tabRectangle.y = firstLabel.getBounds().y;
data.tabRectangle.width = (int)lastLabel.getBounds().getMaxX() - data.tabRectangle.x;
data.tabRectangle.height = (int)lastLabel.getBounds().getMaxY() - data.tabRectangle.y;
}
}
myLastSingRowLayout = data;
return data;
}
@Nullable
protected TabLabel findLastVisibleLabel(SingleRowPassInfo data) {
return myTabs.myInfo2Label.get(data.toLayout.get(data.toLayout.size() - 1));
}
protected void prepareLayoutPassInfo(SingleRowPassInfo data, TabInfo selected) {
data.insets = myTabs.getLayoutInsets();
data.insets.left += myTabs.getFirstTabOffset();
final JBTabsImpl.Toolbar selectedToolbar = myTabs.myInfo2Toolbar.get(selected);
data.hToolbar = selectedToolbar != null && myTabs.myHorizontalSide && !selectedToolbar.isEmpty() ? selectedToolbar : null;
data.vToolbar = selectedToolbar != null && !myTabs.myHorizontalSide && !selectedToolbar.isEmpty() ? selectedToolbar : null;
data.toFitLength = getStrategy().getToFitLength(data);
if (myTabs.isGhostsAlwaysVisible()) {
data.toFitLength -= myTabs.getGhostTabLength() * 2 + (myTabs.getInterTabSpaceLength() * 2);
}
}
protected void updateMoreIconVisibility(SingleRowPassInfo data) {
int counter = 0;
for (TabInfo tabInfo : data.myVisibleInfos) {
if (isTabHidden(tabInfo)) counter++;
}
myMoreIcon.updateCounter(counter);
}
protected void layoutMoreButton(SingleRowPassInfo data) {
if (data.toDrop.size() > 0) {
data.moreRect = getStrategy().getMoreRect(data);
}
}
private void layoutLabelsAndGhosts(final SingleRowPassInfo data) {
if (data.firstGhostVisible || myTabs.isGhostsAlwaysVisible()) {
data.firstGhost = getStrategy().getLayoutRect(data, data.position, myTabs.getGhostTabLength());
myTabs.layout(myLeftGhost, data.firstGhost);
data.position += getStrategy().getLengthIncrement(data.firstGhost.getSize()) + myTabs.getInterTabSpaceLength();
}
int deltaToFit = 0;
if (data.firstGhostVisible || data.lastGhostVisible) {
if (data.requiredLength < data.toFitLength && getStrategy().canBeStretched()) {
deltaToFit = (int)Math.floor((data.toFitLength - data.requiredLength) / (double)data.toLayout.size());
}
}
int totalLength = 0;
int positionStart = data.position;
boolean layoutStopped = false;
for (TabInfo eachInfo : data.toLayout) {
final TabLabel label = myTabs.myInfo2Label.get(eachInfo);
if (layoutStopped) {
label.setActionPanelVisible(false);
final Rectangle rec = getStrategy().getLayoutRect(data, 0, 0);
myTabs.layout(label, rec);
continue;
}
label.setActionPanelVisible(true);
final Dimension eachSize = label.getPreferredSize();
boolean isLast = data.toLayout.indexOf(eachInfo) == data.toLayout.size() - 1;
int length;
if (!isLast || deltaToFit == 0) {
length = getStrategy().getLengthIncrement(eachSize) + deltaToFit;
}
else {
length = data.toFitLength - totalLength;
}
boolean continueLayout = applyTabLayout(data, label, length, deltaToFit);
data.position = getStrategy().getMaxPosition(label.getBounds());
data.position += myTabs.getInterTabSpaceLength();
totalLength = getStrategy().getMaxPosition(label.getBounds()) - positionStart + myTabs.getInterTabSpaceLength();
if (!continueLayout) {
layoutStopped = true;
}
}
for (TabInfo eachInfo : data.toDrop) {
JBTabsImpl.resetLayout(myTabs.myInfo2Label.get(eachInfo));
}
if (data.lastGhostVisible || myTabs.isGhostsAlwaysVisible()) {
data.lastGhost = getStrategy().getLayoutRect(data, data.position, myTabs.getGhostTabLength());
myTabs.layout(myRightGhost, data.lastGhost);
}
}
protected boolean applyTabLayout(SingleRowPassInfo data, TabLabel label, int length, int deltaToFit) {
final Rectangle rec = getStrategy().getLayoutRect(data, data.position, length);
myTabs.layout(label, rec);
label.setAlignmentToCenter((deltaToFit > 0 || myTabs.isEditorTabs()) && getStrategy().isToCenterTextWhenStretched());
return true;
}
protected void recomputeToLayout(final SingleRowPassInfo data) {
calculateRequiredLength(data);
while (true) {
if (data.requiredLength <= data.toFitLength - data.position) break;
if (data.toLayout.size() == 0) break;
final TabInfo first = data.toLayout.get(0);
final TabInfo last = data.toLayout.get(data.toLayout.size() - 1);
if (myRowDropPolicy == RowDropPolicy.first) {
if (first != myTabs.getSelectedInfo()) {
processDrop(data, first, true);
}
else if (last != myTabs.getSelectedInfo()) {
processDrop(data, last, false);
}
else {
break;
}
}
else {
if (last != myTabs.getSelectedInfo()) {
processDrop(data, last, false);
}
else if (first != myTabs.getSelectedInfo()) {
processDrop(data, first, true);
}
else {
break;
}
}
}
for (int i = 1; i < data.myVisibleInfos.size() - 1; i++) {
final TabInfo each = data.myVisibleInfos.get(i);
final TabInfo prev = data.myVisibleInfos.get(i - 1);
final TabInfo next = data.myVisibleInfos.get(i + 1);
if (data.toLayout.contains(each) && data.toDrop.contains(prev)) {
myLeftGhost.setInfo(prev);
}
else if (data.toLayout.contains(each) && data.toDrop.contains(next)) {
myRightGhost.setInfo(next);
}
}
}
protected void calculateRequiredLength(SingleRowPassInfo data) {
for (TabInfo eachInfo : data.myVisibleInfos) {
data.requiredLength += getRequiredLength(eachInfo);
if (myTabs.getTabsPosition() == JBTabsPosition.left || myTabs.getTabsPosition() == JBTabsPosition.right) {
data.requiredLength -= 1;
}
data.toLayout.add(eachInfo);
}
}
protected int getRequiredLength(TabInfo eachInfo) {
TabLabel label = myTabs.myInfo2Label.get(eachInfo);
return getStrategy().getLengthIncrement(label != null ? label.getPreferredSize() : new Dimension())
+ (myTabs.isEditorTabs() ? myTabs.getInterTabSpaceLength() : 0);
}
public boolean isTabHidden(TabInfo tabInfo) {
return myLastSingRowLayout != null && myLastSingRowLayout.toDrop.contains(tabInfo);
}
public class GhostComponent extends JLabel {
private TabInfo myInfo;
private GhostComponent(final RowDropPolicy before, final RowDropPolicy after) {
addMouseListener(new MouseAdapter() {
public void mousePressed(final MouseEvent e) {
if (JBTabsImpl.isSelectionClick(e, true) && myInfo != null) {
myRowDropPolicy = before;
myTabs.select(myInfo, true).doWhenDone(new Runnable() {
public void run() {
myRowDropPolicy = after;
}
});
} else {
MouseEvent event = SwingUtilities.convertMouseEvent(e.getComponent(), e, myTabs);
myTabs.processMouseEvent(event);
}
}
});
}
public void setInfo(@Nullable final TabInfo info) {
myInfo = info;
setToolTipText(info != null ? info.getTooltipText() : null);
}
public void reset() {
JBTabsImpl.resetLayout(this);
setInfo(null);
}
}
private void processDrop(final SingleRowPassInfo data, final TabInfo info, boolean isFirstSide) {
data.requiredLength -= getStrategy().getLengthIncrement(myTabs.myInfo2Label.get(info).getPreferredSize());
data.toDrop.add(info);
data.toLayout.remove(info);
if (data.toDrop.size() == 1) {
data.toFitLength -= data.moreRectAxisSize;
}
if (!data.firstGhostVisible && isFirstSide) {
data.firstGhostVisible = !myTabs.isEditorTabs();
if (!myTabs.isGhostsAlwaysVisible() && !myTabs.isEditorTabs()) {
data.toFitLength -= myTabs.getGhostTabLength();
}
}
else if (!data.lastGhostVisible && !isFirstSide) {
data.lastGhostVisible = !myTabs.isEditorTabs();
if (!myTabs.isGhostsAlwaysVisible() && !myTabs.isEditorTabs()) {
data.toFitLength -= myTabs.getGhostTabLength();
}
}
}
@Override
public int getDropIndexFor(Point point) {
if (myLastSingRowLayout == null) return -1;
int result = -1;
Component c = myTabs.getComponentAt(point);
if (c instanceof JBTabsImpl) {
for (int i = 0; i < myLastSingRowLayout.myVisibleInfos.size() - 1; i++) {
TabLabel first = myTabs.myInfo2Label.get(myLastSingRowLayout.myVisibleInfos.get(i));
TabLabel second = myTabs.myInfo2Label.get(myLastSingRowLayout.myVisibleInfos.get(i + 1));
Rectangle firstBounds = first.getBounds();
Rectangle secondBounds = second.getBounds();
final boolean between;
boolean horizontal = getStrategy() instanceof SingleRowLayoutStrategy.Horizontal;
if (horizontal) {
between = firstBounds.getMaxX() < point.x
&& secondBounds.getX() > point.x
&& firstBounds.y < point.y
&& secondBounds.getMaxY() > point.y;
} else {
between = firstBounds.getMaxY() < point.y
&& secondBounds.getY() > point.y
&& firstBounds.x < point.x
&& secondBounds.getMaxX() > point.x;
}
if (between) {
c = first;
break;
}
}
}
if (c instanceof TabLabel) {
TabInfo info = ((TabLabel)c).getInfo();
int index = myLastSingRowLayout.myVisibleInfos.indexOf(info);
boolean isDropTarget = myTabs.isDropTarget(info);
if (!isDropTarget) {
for (int i = 0; i <= index; i++) {
if (myTabs.isDropTarget(myLastSingRowLayout.myVisibleInfos.get(i))) {
index -= 1;
break;
}
}
result = index;
} else if (index < myLastSingRowLayout.myVisibleInfos.size()) {
result = index;
}
} else if (c instanceof GhostComponent) {
GhostComponent ghost = (GhostComponent)c;
TabInfo info = ghost.myInfo;
if (info != null) {
int index = myLastSingRowLayout.myVisibleInfos.indexOf(info);
index += myLeftGhost == ghost ? -1 : 1;
result = index >= 0 && index < myLastSingRowLayout.myVisibleInfos.size() ? index : -1;
} else {
if (myLastSingRowLayout.myVisibleInfos.size() == 0) {
result = 0;
} else {
result = myLeftGhost == ghost ? 0 : myLastSingRowLayout.myVisibleInfos.size() - 1;
}
}
}
return result;
}
}