initial implementation of scrollable single-row layout for editor tabs (idea.is.internal for now)

This commit is contained in:
Dmitry Jemerov
2012-02-23 20:57:14 +01:00
parent a7a30c9fdc
commit 9f977aaf94
6 changed files with 233 additions and 34 deletions
@@ -17,11 +17,14 @@ package com.intellij.ui.tabs.impl;
import com.intellij.openapi.Disposable;
import com.intellij.openapi.actionSystem.ActionManager;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.wm.IdeFocusManager;
import com.intellij.ui.tabs.JBTabsPosition;
import com.intellij.ui.tabs.TabInfo;
import com.intellij.ui.tabs.TabsUtil;
import com.intellij.ui.tabs.impl.singleRow.ScrollableSingleRowLayout;
import com.intellij.ui.tabs.impl.singleRow.SingleRowLayout;
import com.intellij.ui.tabs.impl.table.TableLayout;
import com.intellij.util.ui.SameColor;
import com.intellij.util.ui.UIUtil;
@@ -40,6 +43,14 @@ public class JBEditorTabs extends JBTabsImpl {
super(project, actionManager, focusManager, parent);
}
@Override
protected SingleRowLayout createSingleRowLayout() {
if (ApplicationManager.getApplication().isInternal()) {
return new ScrollableSingleRowLayout(this);
}
return super.createSingleRowLayout();
}
@Override
public boolean isEditorTabs() {
return true;
@@ -103,11 +103,11 @@ public class JBTabsImpl extends JComponent
private final WeakHashMap<Component, Component> myDeferredToRemove = new WeakHashMap<Component, Component>();
private final SingleRowLayout mySingleRowLayout = new SingleRowLayout(this);
private final SingleRowLayout mySingleRowLayout;
private final TableLayout myTableLayout = new TableLayout(this);
private TabLayout myLayout = mySingleRowLayout;
private TabLayout myLayout;
private LayoutPassInfo myLastLayoutPass;
private TabInfo myLastPaintedSelection;
@@ -206,6 +206,9 @@ public class JBTabsImpl extends JComponent
setUiDecorator(null);
mySingleRowLayout = createSingleRowLayout();
myLayout = mySingleRowLayout;
myPopupListener = new PopupMenuListener() {
public void popupMenuWillBecomeVisible(final PopupMenuEvent e) {
}
@@ -228,6 +231,15 @@ public class JBTabsImpl extends JComponent
}
}
});
addMouseWheelListener(new MouseWheelListener() {
@Override
public void mouseWheelMoved(MouseWheelEvent e) {
if (mySingleRowLayout.myLastSingRowLayout != null) {
mySingleRowLayout.scroll(e.getUnitsToScroll() * 10);
revalidateAndRepaint(false);
}
}
});
myAnimator = new Animator("JBTabs Attractions", 2, 500, true) {
public void paintNow(final int frame, final int totalFrames, final int cycle) {
@@ -284,6 +296,10 @@ public class JBTabsImpl extends JComponent
};
}
protected SingleRowLayout createSingleRowLayout() {
return new SingleRowLayout(this);
}
public JBTabs setNavigationActionBinding(String prevActionId, String nextActionId) {
if (myNextAction != null) {
@@ -2491,8 +2507,6 @@ public class JBTabsImpl extends JComponent
}
private void updateContainer(boolean forced, final boolean layoutNow) {
final TabLabel selectedLabel = getSelectedLabel();
for (TabInfo each : myVisibleInfos) {
final JComponent eachComponent = each.getComponent();
if (getSelectedInfo() == each && getSelectedInfo() != null) {
@@ -2518,6 +2532,7 @@ public class JBTabsImpl extends JComponent
}
}
mySingleRowLayout.scrollSelectionInView(myVisibleInfos);
relayout(forced, layoutNow);
}
@@ -1,5 +1,5 @@
/*
* Copyright 2000-2009 JetBrains s.r.o.
* Copyright 2000-2012 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.
@@ -250,6 +250,10 @@ public class TabLabel extends JPanel {
@Override
public Dimension getPreferredSize() {
final Dimension size = super.getPreferredSize();
if (myActionPanel != null && !myActionPanel.isVisible()) {
final Dimension actionPanelSize = myActionPanel.getPreferredSize();
size.width += actionPanelSize.width;
}
final JBTabsPosition pos = myTabs.getTabsPosition();
switch (pos) {
@@ -526,6 +530,12 @@ public class TabLabel extends JPanel {
}
}
public void setActionPanelVisible(boolean visible) {
if (myActionPanel != null) {
myActionPanel.setVisible(visible);
}
}
@Override
public String toString() {
return myInfo.getText();
@@ -0,0 +1,115 @@
/*
* Copyright 2000-2012 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.ui.tabs.TabInfo;
import com.intellij.ui.tabs.impl.JBTabsImpl;
import com.intellij.ui.tabs.impl.TabLabel;
import java.util.List;
/**
* @author yole
*/
public class ScrollableSingleRowLayout extends SingleRowLayout {
private int myScrollOffset = 0;
private boolean myScrollSelectionInViewPending = false;
public ScrollableSingleRowLayout(final JBTabsImpl tabs) {
super(tabs);
}
@Override
int getScrollOffset() {
return myScrollOffset;
}
@Override
public void scroll(int units) {
myScrollOffset += units;
if (myLastSingRowLayout != null) {
clampScrollOffsetToBounds(myLastSingRowLayout);
}
}
private void clampScrollOffsetToBounds(SingleRowPassInfo data) {
if (data.requiredLength < data.toFitLength) {
myScrollOffset = 0;
}
else {
myScrollOffset = Math.max(0, Math.min(myScrollOffset, data.requiredLength - data.toFitLength + getStrategy().getMoreRectAxisSize()));
}
}
@Override
public void scrollSelectionInView(List<TabInfo> visibleInfos) {
myScrollSelectionInViewPending = true;
}
private void doScrollSelectionInView(SingleRowPassInfo passInfo) {
int offset = -myScrollOffset;
for (TabInfo info : passInfo.myVisibleInfos) {
final int length = getRequiredLength(info);
if (info == myTabs.getSelectedInfo()) {
if (offset < 0) {
scroll(offset);
}
else if (offset + length > passInfo.toFitLength - getStrategy().getMoreRectAxisSize()) {
scroll(offset + length - passInfo.toFitLength + getStrategy().getMoreRectAxisSize());
}
break;
}
offset += length;
}
}
@Override
protected void recomputeToLayout(SingleRowPassInfo data) {
calculateRequiredLength(data);
clampScrollOffsetToBounds(data);
if (myScrollSelectionInViewPending || !data.layoutSize.equals(myLastSingRowLayout.layoutSize)) {
myScrollSelectionInViewPending = false;
doScrollSelectionInView(data);
}
}
protected void layoutMoreButton(SingleRowPassInfo data) {
if (data.requiredLength > data.toFitLength) {
data.moreRect = getStrategy().getMoreRect(data);
}
}
@Override
protected void updateMoreIconVisibility(SingleRowPassInfo data) {
myMoreIcon.setPainted(data.requiredLength > data.toFitLength);
}
@Override
protected boolean applyTabLayout(SingleRowPassInfo data, TabLabel label, int length, int deltaToFit) {
if (data.requiredLength > data.toFitLength) {
length = getStrategy().getLengthIncrement(label.getPreferredSize());
final int moreRectSize = getStrategy().getMoreRectAxisSize();
if (data.position + length > data.toFitLength - moreRectSize) {
super.applyTabLayout(data, label, data.toFitLength - data.position - moreRectSize - 4, deltaToFit);
label.setAlignmentToCenter(false);
label.setActionPanelVisible(false);
return false;
}
}
label.setActionPanelVisible(true);
return super.applyTabLayout(data, label, length, deltaToFit);
}
}
@@ -63,7 +63,6 @@ public class SingleRowLayout extends TabLayout {
private RowDropPolicy myRowDropPolicy = RowDropPolicy.first;
@Override
public boolean isSideComponentOnTabs() {
return getStrategy().isSideComponentOnTabs();
@@ -108,7 +107,8 @@ public class SingleRowLayout extends TabLayout {
if (!myTabs.myForcedRelayout &&
myLastSingRowLayout != null &&
myLastSingRowLayout.contentCount == myTabs.getTabCount() &&
myLastSingRowLayout.layoutSize.equals(myTabs.getSize())) {
myLastSingRowLayout.layoutSize.equals(myTabs.getSize()) &&
myLastSingRowLayout.scrollOffset == getScrollOffset()) {
for (TabInfo each : data.myVisibleInfos) {
final TabLabel eachLabel = myTabs.myInfo2Label.get(each);
if (!eachLabel.isValid()) {
@@ -125,10 +125,18 @@ public class SingleRowLayout extends TabLayout {
return layoutLabels;
}
int getScrollOffset() {
return 0;
}
public void scroll(int units) {
}
public void scrollSelectionInView(List<TabInfo> visibleInfos) {
}
public LayoutPassInfo layoutSingleRow(List<TabInfo> visibleInfos) {
SingleRowPassInfo data = new SingleRowPassInfo(this, visibleInfos);
final TabInfo selected = myTabs.getSelectedInfo();
final JBTabsImpl.Toolbar selectedToolbar = myTabs.myInfo2Toolbar.get(selected);
final boolean layoutLabels = checkLayoutLabels(data);
if (!layoutLabels) {
@@ -136,24 +144,20 @@ public class SingleRowLayout extends TabLayout {
}
data.insets = myTabs.getLayoutInsets();
data.hToolbar = selectedToolbar != null && myTabs.myHorizontalSide && !selectedToolbar.isEmpty() ? selectedToolbar : null;
data.vToolbar = selectedToolbar != null && !myTabs.myHorizontalSide && !selectedToolbar.isEmpty() ? selectedToolbar : null;
final TabInfo selected = myTabs.getSelectedInfo();
prepareLayoutPassInfo(data, selected);
myTabs.resetLayout(layoutLabels || myTabs.isHideTabs());
if (layoutLabels && !myTabs.isHideTabs()) {
data.position = getStrategy().getStartPosition(data);
data.position = getStrategy().getStartPosition(data) - getScrollOffset();
recomputeToLayout(data);
layoutLabelsAndGhosts(data);
if (data.toDrop.size() > 0) {
data.moreRect = getStrategy().getMoreRect(data);
}
layoutMoreButton(data);
}
if (selected != null) {
@@ -161,7 +165,7 @@ public class SingleRowLayout extends TabLayout {
getStrategy().layoutComp(data);
}
myMoreIcon.setPainted(data.toLayout.size() > 0 && data.myVisibleInfos.size() > data.toLayout.size());
updateMoreIconVisibility(data);
data.tabRectangle = new Rectangle();
@@ -180,6 +184,29 @@ public class SingleRowLayout extends TabLayout {
return data;
}
protected void prepareLayoutPassInfo(SingleRowPassInfo data, TabInfo selected) {
data.insets = myTabs.getLayoutInsets();
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) {
myMoreIcon.setPainted(data.toLayout.size() > 0 && data.myVisibleInfos.size() > data.toLayout.size());
}
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());
@@ -196,8 +223,17 @@ public class SingleRowLayout extends TabLayout {
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;
@@ -209,15 +245,15 @@ public class SingleRowLayout extends TabLayout {
else {
length = data.toFitLength - totalLength;
}
final Rectangle rec = getStrategy().getLayoutRect(data, data.position, length);
myTabs.layout(label, rec);
label.setAlignmentToCenter((deltaToFit > 0 || myTabs.isEditorTabs()) && getStrategy().isToCenterTextWhenStretched());
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) {
@@ -230,19 +266,17 @@ public class SingleRowLayout extends TabLayout {
}
}
protected boolean applyTabLayout(SingleRowPassInfo data, TabLabel label, int length, int deltaToFit) {
final Rectangle rec = getStrategy().getLayoutRect(data, data.position, length);
myTabs.layout(label, rec);
private void recomputeToLayout(final SingleRowPassInfo data) {
data.toFitLength = getStrategy().getToFitLength(data);
label.setAlignmentToCenter((deltaToFit > 0 || myTabs.isEditorTabs()) && getStrategy().isToCenterTextWhenStretched());
return true;
}
if (myTabs.isGhostsAlwaysVisible()) {
data.toFitLength -= myTabs.getGhostTabLength() * 2 + (myTabs.getInterTabSpaceLength() * 2);
}
for (TabInfo eachInfo : data.myVisibleInfos) {
data.requiredLength += getStrategy().getLengthIncrement(myTabs.myInfo2Label.get(eachInfo).getPreferredSize())
+ (myTabs.isEditorTabs() ? myTabs.getInterTabSpaceLength() : 0);
data.toLayout.add(eachInfo);
}
protected void recomputeToLayout(final SingleRowPassInfo data) {
calculateRequiredLength(data);
while (true) {
if (data.requiredLength <= data.toFitLength - data.position) break;
@@ -292,6 +326,18 @@ public class SingleRowLayout extends TabLayout {
}
protected void calculateRequiredLength(SingleRowPassInfo data) {
for (TabInfo eachInfo : data.myVisibleInfos) {
data.requiredLength += getRequiredLength(eachInfo);
data.toLayout.add(eachInfo);
}
}
protected int getRequiredLength(TabInfo eachInfo) {
return getStrategy().getLengthIncrement(myTabs.myInfo2Label.get(eachInfo).getPreferredSize())
+ (myTabs.isEditorTabs() ? myTabs.getInterTabSpaceLength() : 0);
}
public class GhostComponent extends JLabel {
private TabInfo myInfo;
@@ -1,5 +1,5 @@
/*
* Copyright 2000-2009 JetBrains s.r.o.
* Copyright 2000-2012 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.
@@ -51,6 +51,7 @@ public class SingleRowPassInfo extends LayoutPassInfo {
private final JBTabsImpl myTabs;
public JComponent comp;
public Rectangle tabRectangle;
int scrollOffset;
public SingleRowPassInfo(SingleRowLayout layout, List<TabInfo> visibleInfos) {
@@ -61,6 +62,7 @@ public class SingleRowPassInfo extends LayoutPassInfo {
toLayout = new ArrayList<TabInfo>();
toDrop = new ArrayList<TabInfo>();
moreRectAxisSize = layout.getStrategy().getMoreRectAxisSize();
scrollOffset = layout.getScrollOffset();
}
public TabInfo getPreviousFor(final TabInfo info) {