IDEA-253426 Implement "Show pinned tabs in a separate row" for single row layout

GitOrigin-RevId: 11ccc9b7f90ec47ed518230713e23be5b8f1dfec
This commit is contained in:
Vassiliy.Kudryashov
2021-03-22 20:06:35 +00:00
committed by intellij-monorepo-bot
parent d06b9eb6cb
commit 1418aaa7fb
5 changed files with 447 additions and 157 deletions
@@ -31,6 +31,7 @@ import com.intellij.ui.tabs.impl.singleRow.ScrollableSingleRowLayout;
import com.intellij.ui.tabs.impl.singleRow.SingleRowLayout;
import com.intellij.ui.tabs.impl.singleRow.SingleRowPassInfo;
import com.intellij.ui.tabs.impl.table.TableLayout;
import com.intellij.ui.tabs.impl.table.TablePassInfo;
import com.intellij.ui.tabs.impl.tabsLayout.TabsLayout;
import com.intellij.ui.tabs.impl.tabsLayout.TabsLayoutCallback;
import com.intellij.ui.tabs.impl.tabsLayout.TabsLayoutInfo;
@@ -199,6 +200,7 @@ public class JBTabsImpl extends JComponent
private MouseListener myTabsLayoutMouseListener;
private MouseMotionListener myTabsLayoutMouseMotionListener;
private MouseWheelListener myTabsLayoutMouseWheelListener;
private boolean mySingleRow = true;
protected JBTabsBorder createTabBorder() {
return new JBDefaultTabsBorder(this);
@@ -405,7 +407,7 @@ public class JBTabsImpl extends JComponent
setUiDecorator(null);
mySingleRowLayout = createSingleRowLayout();
myLayout = mySingleRowLayout;
setLayout(mySingleRowLayout);
mySplitter.getDivider().setOpaque(false);
@@ -457,19 +459,28 @@ public class JBTabsImpl extends JComponent
if (mySingleRowLayout.myLastSingRowLayout != null) {
mySingleRowLayout.scroll((int)Math.round(units * mySingleRowLayout.getScrollUnitIncrement()));
revalidateAndRepaint(false);
} else if (myTableLayout.myLastTableLayout != null) {
myTableLayout.scroll((int)Math.round(units * myTableLayout.getScrollUnitIncrement()));
revalidateAndRepaint(false);
}
});
AWTEventListener listener = new AWTEventListener() {
final Alarm afterScroll = new Alarm(parentDisposable);
@Override
public void eventDispatched(AWTEvent event) {
if (mySingleRowLayout.myLastSingRowLayout == null) return;
Rectangle tabRectangle = null;
if (mySingleRowLayout.myLastSingRowLayout != null) {
tabRectangle = mySingleRowLayout.myLastSingRowLayout.tabRectangle;
} else if (myTableLayout.myLastTableLayout != null) {
tabRectangle = myTableLayout.myLastTableLayout.tabRectangle;
}
if (tabRectangle == null) return;
MouseEvent me = (MouseEvent)event;
Point point = me.getPoint();
SwingUtilities.convertPointToScreen(point, me.getComponent());
Rectangle rect = getVisibleRect();
rect = rect.intersection(mySingleRowLayout.myLastSingRowLayout.tabRectangle);
rect = rect.intersection(tabRectangle);
Point p = rect.getLocation();
SwingUtilities.convertPointToScreen(p, JBTabsImpl.this);
rect.setLocation(p);
@@ -552,12 +563,21 @@ public class JBTabsImpl extends JComponent
@Override
protected void paintChildren(Graphics g) {
super.paintChildren(g);
if (Registry.is("ui.no.bangs.and.whistles", false)) {
if (Registry.is("ui.no.bangs.and.whistles", false) || !isSingleRow() || !UISettings.getInstance().getHideTabsIfNeeded()) {
return;
}
if (Registry.is("ide.editor.tabs.show.fadeout") && !getTabsPosition().isSide() && myMoreToolbar.getComponent().isShowing()) {
JComponent more = myMoreToolbar.getComponent();
if (Registry.is("ide.editor.tabs.show.fadeout") && !getTabsPosition().isSide() && more.isShowing()) {
TabInfo selectedInfo = getSelectedInfo();
final JBTabsImpl.Toolbar selectedToolbar = selectedInfo != null ? myInfo2Toolbar.get(selectedInfo) : null;
int width = JBUI.scale(MathUtil.clamp(Registry.intValue("ide.editor.tabs.fadeout.width", 10), 1, 200));
Rectangle moreRect = getMoreRect();
Rectangle labelsArea = null;
int moreY = 0;
int moreHeight = 0;
boolean showRightFadeout = false;
boolean showLeftFadeout = false;
for (TabLabel label : myInfo2Label.values()) {
@@ -567,18 +587,25 @@ public class JBTabsImpl extends JComponent
labelsArea = labelsArea.union(label.getBounds());
}
showLeftFadeout |= label.getX() < 0;
showRightFadeout |= label.getWidth() < label.getPreferredSize().width - 1;
boolean needShowRightFadeout = moreRect != null
&& label.getX() + label.getPreferredSize().width > moreRect.x
&& label.getY() == moreRect.y;
if (needShowRightFadeout && !showRightFadeout) {
moreY = label.getY();
moreHeight = label.getHeight();
}
showRightFadeout |= needShowRightFadeout;
}
Color transparent = ColorUtil.withAlpha(UIUtil.getPanelBackground(), 0);
if (showLeftFadeout) {
Rectangle leftSide = new Rectangle(0, labelsArea.y, width, labelsArea.height);
Rectangle leftSide = new Rectangle(0, more.getY() - 1, width, more.getHeight() + 1);
((Graphics2D)g).setPaint(
new GradientPaint(leftSide.x, leftSide.y, UIUtil.getPanelBackground(), leftSide.x + leftSide.width,
leftSide.y, transparent));
((Graphics2D)g).fill(leftSide);
}
if (showRightFadeout) {
Rectangle rightSide = new Rectangle(myMoreToolbar.getComponent().getX() - 1 - width, labelsArea.y, width, labelsArea.height);
Rectangle rightSide = new Rectangle(myMoreToolbar.getComponent().getX() - 1 - width, moreY, width, moreHeight);
((Graphics2D)g).setPaint(
new GradientPaint(rightSide.x, rightSide.y, transparent, rightSide.x + rightSide.width, rightSide.y,
UIUtil.getPanelBackground()));
@@ -605,20 +632,23 @@ public class JBTabsImpl extends JComponent
info.revalidate();
label.setTabActions(info.getTabLabelActions());
}
boolean oldHideTabsIfNeeded = mySingleRowLayout instanceof ScrollableSingleRowLayout;
boolean newHideTabsIfNeeded = uiSettings.getHideTabsIfNeeded();
if (oldHideTabsIfNeeded != newHideTabsIfNeeded) {
updateRowLayout();
}
updateRowLayout();
}
private void updateRowLayout() {
boolean wasSingleRow = isSingleRow();
mySingleRowLayout = createSingleRowLayout();
if (wasSingleRow) {
myLayout = mySingleRowLayout;
boolean useTableLayout = !isSingleRow();
useTableLayout |= getTabsPosition() == JBTabsPosition.top
&& supportsTableLayoutAsSingleRow()
&& UISettings.getInstance().getState().getShowPinnedTabsInASeparateRow();
TabLayout layout = useTableLayout ? myTableLayout : mySingleRowLayout;
if (setLayout(layout)) {
relayout(true, true);
}
relayout(true, true);
}
protected boolean supportsTableLayoutAsSingleRow() {
return false;
}
protected SingleRowLayout createSingleRowLayout() {
@@ -692,7 +722,7 @@ public class JBTabsImpl extends JComponent
}
public boolean isDragOut(TabLabel label, int deltaX, int deltaY) {
if (NEW_TABS) {
if (!NEW_TABS) {
return getEffectiveLayout().isDragOut(label, deltaX, deltaY);
} else {
return myTabsLayout.isDragOut(label, deltaX, deltaY);
@@ -703,7 +733,7 @@ public class JBTabsImpl extends JComponent
if (NEW_TABS) {
return myTabsLayout != null && myTabsLayout.ignoreTabLabelLimitedWidthWhenPaint();
} else {
return getEffectiveLayout() instanceof ScrollableSingleRowLayout;
return myLayout instanceof ScrollableSingleRowLayout /*|| myLayout instanceof TableLayout*/;
}
}
@@ -955,13 +985,27 @@ public class JBTabsImpl extends JComponent
}
private Rectangle getMoreRect() {
SingleRowPassInfo lastLayout = mySingleRowLayout.myLastSingRowLayout;
return lastLayout != null ? lastLayout.moreRect : null;
if (myLayout instanceof SingleRowLayout) {
SingleRowPassInfo lastLayout = mySingleRowLayout.myLastSingRowLayout;
return lastLayout != null ? lastLayout.moreRect : null;
}
if (myLayout instanceof TableLayout) {
TablePassInfo lastLayout = myTableLayout.myLastTableLayout;
return lastLayout != null ? lastLayout.moreRect : null;
}
return null;
}
private Rectangle getTitleRect() {
SingleRowPassInfo lastLayout = mySingleRowLayout.myLastSingRowLayout;
return lastLayout != null ? lastLayout.titleRect : null;
if (myLayout instanceof SingleRowLayout) {
SingleRowPassInfo lastLayout = mySingleRowLayout.myLastSingRowLayout;
return lastLayout != null ? lastLayout.titleRect : null;
}
if (myLayout instanceof TableLayout) {
TablePassInfo lastLayout = myTableLayout.myLastTableLayout;
return lastLayout != null ? lastLayout.titleRect : null;
}
return null;
}
@Override
@@ -977,18 +1021,16 @@ public class JBTabsImpl extends JComponent
@Override
public boolean canShowMorePopup() {
if (NEW_TABS) {
return myTabsLayout instanceof MorePopupAware && ((MorePopupAware)myTabsLayout).canShowMorePopup();
}
return getMoreRect() != null;
if (myLayout instanceof MorePopupAware)
return ((MorePopupAware)myLayout).canShowMorePopup();
Rectangle moreRect = getMoreRect();
return moreRect != null;
}
@Override
public void showMorePopup() {
if (NEW_TABS) {
if (myTabsLayout instanceof MorePopupAware) {
((MorePopupAware)myTabsLayout).showMorePopup();
}
if (myLayout instanceof MorePopupAware) {
((MorePopupAware)myLayout).showMorePopup();
return;
}
@@ -1775,12 +1817,13 @@ public class JBTabsImpl extends JComponent
}
}
if (isSingleRow()) {
if (myLayout instanceof SingleRowLayout) {
mySingleRowLayout.scrollSelectionInView();
myLastLayoutPass = mySingleRowLayout.layoutSingleRow(visible);
Rectangle moreRect = getMoreRect();
if (moreRect != null) {
Dimension preferredSize = myMoreToolbar.getComponent().getPreferredSize();
JComponent mComponent = myMoreToolbar.getComponent();
if (moreRect != null && !moreRect.isEmpty()) {
Dimension preferredSize = mComponent.getPreferredSize();
Rectangle bounds = new Rectangle(moreRect);
int xDiff = (bounds.width - preferredSize.width) / 2;
int yDiff = (bounds.height - preferredSize.height) / 2;
@@ -1788,12 +1831,12 @@ public class JBTabsImpl extends JComponent
bounds.width -= 2 * xDiff;
bounds.y += yDiff;
bounds.height -= 2* yDiff;
myMoreToolbar.getComponent().setBounds(bounds);
mComponent.setBounds(bounds);
} else {
myMoreToolbar.getComponent().setBounds(new Rectangle());
mComponent.setBounds(new Rectangle());
}
Rectangle titleRect = getTitleRect();
if (titleRect != null) {
if (titleRect != null && !titleRect.isEmpty()) {
Dimension preferredSize = myTitleWrapper.getPreferredSize();
Rectangle bounds = new Rectangle(titleRect);
JBInsets.removeFrom(bounds, getLayoutInsets());
@@ -1817,7 +1860,9 @@ public class JBTabsImpl extends JComponent
}
}
else {
myLastLayoutPass = myTableLayout.layoutTable(visible);
//TableLayout does layout 'Title' and 'More' by itself
myTableLayout.scrollSelectionInView();
myLastLayoutPass = myTableLayout.layoutTable(visible, myTitleWrapper, myMoreToolbar.getComponent());
mySingleRowLayout.myLastSingRowLayout = null;
}
@@ -2346,8 +2391,11 @@ public class JBTabsImpl extends JComponent
}
}
}
mySingleRowLayout.scrollSelectionInView();
if (myLayout == mySingleRowLayout) {
mySingleRowLayout.scrollSelectionInView();
} else if (myLayout == myTableLayout) {
myTableLayout.scrollSelectionInView();
}
relayout(forced, layoutNow);
}
@@ -2372,7 +2420,8 @@ public class JBTabsImpl extends JComponent
myForcedRelayout = forced;
}
if (myMoreToolbar != null) {
myMoreToolbar.getComponent().setVisible(getEffectiveLayout() instanceof ScrollableSingleRowLayout);
myMoreToolbar.getComponent().setVisible(getEffectiveLayout() instanceof ScrollableSingleRowLayout ||
getEffectiveLayout() instanceof TableLayout);
}
revalidateAndRepaint(layoutNow);
}
@@ -2752,13 +2801,17 @@ public class JBTabsImpl extends JComponent
@Override
public JBTabsPresentation setSingleRow(boolean singleRow) {
myLayout = singleRow ? mySingleRowLayout : myTableLayout;
relayout(true, false);
mySingleRow = singleRow;
updateRowLayout();
return this;
}
private boolean setLayout(TabLayout layout) {
if (myLayout == layout) return false;
myLayout = layout;
return true;
}
public int getSeparatorWidth() {
return mySeparatorWidth;
}
@@ -2769,11 +2822,7 @@ public class JBTabsImpl extends JComponent
@Override
public boolean isSingleRow() {
if (!NEW_TABS) {
return getEffectiveLayout() == mySingleRowLayout;
} else {
return myTabsLayout.isSingleRow();
}
return mySingleRow;
}
public boolean isSideComponentVertical() {
@@ -2789,8 +2838,7 @@ public class JBTabsImpl extends JComponent
}
public TabLayout getEffectiveLayout() {
if (myLayout == myTableLayout && getTabsPosition() == JBTabsPosition.top) return myTableLayout;
return mySingleRowLayout;
return myLayout;
}
@Override
@@ -2,22 +2,30 @@
package com.intellij.ui.tabs.impl.table;
import com.intellij.ide.ui.UISettings;
import com.intellij.openapi.ui.JBMenuItem;
import com.intellij.openapi.ui.JBPopupMenu;
import com.intellij.ui.tabs.TabInfo;
import com.intellij.ui.tabs.TabsUtil;
import com.intellij.ui.tabs.impl.JBTabsImpl;
import com.intellij.ui.tabs.impl.LayoutPassInfo;
import com.intellij.ui.tabs.impl.TabLabel;
import com.intellij.ui.tabs.impl.TabLayout;
import com.intellij.ui.tabs.impl.*;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.ui.GraphicsUtil;
import com.intellij.util.ui.JBFont;
import com.intellij.util.ui.JBUI;
import org.intellij.lang.annotations.MagicConstant;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.swing.*;
import java.awt.*;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
public class TableLayout extends TabLayout {
public class TableLayout extends TabLayout implements MorePopupAware {
private int myScrollOffset = 0;
private boolean myScrollSelectionInViewPending = false;
private final JBTabsImpl myTabs;
final JBTabsImpl myTabs;
public TablePassInfo myLastTableLayout;
@@ -26,142 +34,260 @@ public class TableLayout extends TabLayout {
}
private TablePassInfo computeLayoutTable(List<TabInfo> visibleInfos) {
final TablePassInfo data = new TablePassInfo(myTabs, visibleInfos);
final TablePassInfo data = new TablePassInfo(this, visibleInfos);
if (myTabs.isHideTabs()) {
return data;
}
boolean singleRow = myTabs.isSingleRow();
boolean showPinnedTabsSeparately = UISettings.getInstance().getState().getShowPinnedTabsInASeparateRow();
boolean scrollable = UISettings.getInstance().getHideTabsIfNeeded() && singleRow;
int titleWidth = myTabs.myTitleWrapper.getPreferredSize().width;
int eachXPinned = data.toFitRec.x;
int eachXUnpinned = data.toFitRec.x;
data.titleRect.setBounds(data.toFitRec.x, data.toFitRec.y, titleWidth, myTabs.myHeaderFitSize.height);
data.moreRect.setBounds(data.toFitRec.x + data.toFitRec.width, data.toFitRec.y, 0, myTabs.myHeaderFitSize.height);
calculateLengths(data);
int eachX = data.titleRect.x + data.titleRect.width;
Insets insets = myTabs.getLayoutInsets();
int eachY = insets.top;
TableRow eachTableRow = new TableRow(data);
data.table.add(eachTableRow);
int requiredRowsPinned = 0;
int requiredRowsUnpinned = 0;
final int maxX = data.toFitRec.x + data.toFitRec.width;
final int maxX = data.moreRect.x - 1;
int hGap = myTabs.getTabHGap();
boolean showPinnedTabsSeparately = UISettings.getInstance().getState().getShowPinnedTabsInASeparateRow();
for (TabInfo eachInfo : data.myVisibleInfos) {
final TabLabel eachLabel = myTabs.myInfo2Label.get(eachInfo);
TabLabel eachLabel = myTabs.getTabLabel(eachInfo);
boolean pinned = eachLabel.isPinned();
final Dimension size = eachLabel.getNotStrictPreferredSize();
int width = size.width + hGap;
int width = data.lengths.get(eachInfo);
if (!pinned || !showPinnedTabsSeparately) {
data.requiredLength += width;
}
if (pinned && showPinnedTabsSeparately) {
if (eachXPinned + width >= maxX) {
requiredRowsPinned++;
eachXPinned = data.toFitRec.x;
}
else if (requiredRowsPinned == 0) {
if (requiredRowsPinned == 0) {
requiredRowsPinned = 1;
}
myTabs.layout(eachLabel, eachXPinned, 0, size.width, 1);
eachXPinned += width;
myTabs.layout(eachLabel, eachX, eachY, width, myTabs.myHeaderFitSize.height);
data.bounds.put(eachInfo, eachLabel.getBounds());
eachX += width + hGap;
}
else {
if (eachXUnpinned + size.width + hGap >= maxX) {
if ((!scrollable && eachX + width + hGap > maxX && !singleRow) || (showPinnedTabsSeparately && eachLabel.isNextToLastPinned())) {
requiredRowsUnpinned++;
eachXUnpinned = data.toFitRec.x;
eachY += myTabs.myHeaderFitSize.height;
eachX = data.toFitRec.x;
}
else if (requiredRowsUnpinned == 0) {
requiredRowsUnpinned = 1;
}
myTabs.layout(eachLabel, eachXUnpinned, 0, size.width, 1);
eachXUnpinned += width;
if (scrollable) {
if (eachX - getScrollOffset() + width + hGap > maxX) {
width = Math.max(0, maxX - eachX + getScrollOffset());
data.invisible.add(eachInfo);
}
}
myTabs.layout(eachLabel, eachX - getScrollOffset(), eachY, width == 1 ? 0 : width, myTabs.myHeaderFitSize.height);
Rectangle rectangle = new Rectangle(myTabs.myHeaderFitSize);
data.bounds.put(eachInfo, eachLabel.getBounds());
int intersection = eachLabel.getBounds().intersection(rectangle).width;
if (scrollable && intersection < eachLabel.getBounds().width) {
data.invisible.add(eachInfo);
}
eachX += width + hGap;
}
}
if (requiredRowsPinned > 0 && requiredRowsUnpinned > 0) data.moreRect.y += myTabs.myHeaderFitSize.height /*+ myTabs.getSeparatorWidth()*/;
eachXPinned = data.toFitRec.x;
eachXUnpinned = data.toFitRec.x;
if (data.invisible.isEmpty()) {
data.moreRect.setBounds(0, 0, 0, 0);
}
eachX = data.toFitRec.x + titleWidth;
for (TabInfo eachInfo : data.myVisibleInfos) {
final TabLabel eachLabel = myTabs.myInfo2Label.get(eachInfo);
final Dimension size = eachLabel.getNotStrictPreferredSize();
final TabLabel eachLabel = myTabs.getTabLabel(eachInfo);
boolean pinned = eachLabel.isPinned();
int width = size.width + hGap;
int width = data.lengths.get(eachInfo);
if (pinned && showPinnedTabsSeparately) {
if (eachXPinned + width <= maxX) {
eachTableRow.add(eachInfo, width);
eachXPinned += width;
} else {
eachTableRow = new TableRow(data);
data.table.add(eachTableRow);
eachXPinned = data.toFitRec.x + width;
eachTableRow.add(eachInfo, width);
eachTableRow.add(eachInfo, width);
eachX += width;
}
else {
boolean useSameRow = singleRow || eachX + /*size.*/width + hGap <= maxX;
if (showPinnedTabsSeparately && eachLabel.isNextToLastPinned()) {
useSameRow = false;
}
} else {
if (eachXUnpinned + size.width + hGap <= maxX && (!showPinnedTabsSeparately || !eachLabel.isNextToLastPinned())) {
if (useSameRow) {
eachTableRow.add(eachInfo, width);
eachXUnpinned += width;
} else {
eachX += width;
}
else {
eachTableRow = new TableRow(data);
data.table.add(eachTableRow);
eachXUnpinned = data.toFitRec.x + width;
eachX = data.toFitRec.x + titleWidth + width;
eachTableRow.add(eachInfo, width);
}
}
}
if (myScrollSelectionInViewPending) {
myScrollSelectionInViewPending = false;
doScrollSelectionInView(data);
}
clampScrollOffsetToBounds(data);
return data;
}
public boolean isLastRow(TabInfo info) {
if (info == null) return false;
List<TableRow> rows = myLastTableLayout.table;
if (rows.size() > 0) {
for (TabInfo tabInfo : rows.get(rows.size() - 1).myColumns) {
if (tabInfo == info) return true;
}
}
return false;
}
public LayoutPassInfo layoutTable(List<TabInfo> visibleInfos) {
myTabs.resetLayout(true);
Insets insets = myTabs.getLayoutInsets();
int eachY = insets.top;
TablePassInfo data = new TablePassInfo(myTabs, visibleInfos);
private void calculateLengths(TablePassInfo data) {
boolean compressible = isCompressible();
boolean showPinnedTabsSeparately = UISettings.getInstance().getState().getShowPinnedTabsInASeparateRow();
if (!myTabs.isHideTabs()) {
data = computeLayoutTable(visibleInfos);
insets = myTabs.getLayoutInsets();
eachY = insets.top;
int eachX;
for (TableRow eachRow : data.table) {
eachX = insets.left;
for (int i = 0; i < eachRow.myColumns.size(); i++) {
TabInfo tabInfo = eachRow.myColumns.get(i);
final TabLabel label = myTabs.myInfo2Label.get(tabInfo);
int width;
if (label.isPinned() && showPinnedTabsSeparately) {
width = label.getNotStrictPreferredSize().width;
}
else {
width = label.getPreferredSize().width;
}
myTabs.layout(label, eachX, eachY, width, myTabs.myHeaderFitSize.height);
label.setAlignmentToCenter(false);
boolean lastCell = i == eachRow.myColumns.size() - 1;
eachX += width + (lastCell ? 0 : myTabs.getTabHGap());
int standardLengthToFit = data.moreRect.x - (data.titleRect.x + data.titleRect.width);
if (compressible || showPinnedTabsSeparately) {
if (showPinnedTabsSeparately) {
List<TabInfo> pinned = ContainerUtil.filter(data.myVisibleInfos, info -> info.isPinned());
calculateCompressibleLengths(pinned, data, standardLengthToFit + data.moreRect.width);
List<TabInfo> unpinned = ContainerUtil.filter(data.myVisibleInfos, info -> !info.isPinned());
if (compressible) {
calculateCompressibleLengths(unpinned, data, pinned.isEmpty()
? standardLengthToFit + data.moreRect.width
: standardLengthToFit + data.titleRect.width + data.moreRect.width);
}
eachY += myTabs.myHeaderFitSize.height;
else {
calculateRawLengths(unpinned, data);
if (getTotalLength(unpinned, data) > standardLengthToFit) {
int moreWidth = myTabs.isSingleRow() ? myTabs.myMoreToolbar.getComponent().getPreferredSize().width : 0;
data.moreRect.setBounds(data.toFitRec.x + data.toFitRec.width - moreWidth, /*data.toFitRec.y*/myTabs.getLayoutInsets().top, moreWidth, myTabs.myHeaderFitSize.height /*- myTabs.getSeparatorWidth()*/);
calculateRawLengths(unpinned, data);
}
}
}
else {
calculateCompressibleLengths(data.myVisibleInfos, data, standardLengthToFit);
}
}
else {//both scrollable and multi-row
calculateRawLengths(data.myVisibleInfos, data);
if (getTotalLength(data.myVisibleInfos, data) > standardLengthToFit) {
int moreWidth = myTabs.isSingleRow() ? myTabs.myMoreToolbar.getComponent().getPreferredSize().width : 0;
data.moreRect.setBounds(data.toFitRec.x + data.toFitRec.width - moreWidth, data.toFitRec.y, moreWidth, myTabs.myHeaderFitSize.height);
calculateRawLengths(data.myVisibleInfos, data);
}
}
}
private static int getTotalLength(@NotNull List<TabInfo> list, @NotNull TablePassInfo data) {
int total = 0;
for (TabInfo info : list) {
total += data.lengths.get(info);
}
return total;
}
private boolean isCompressible() {
return myTabs.isSingleRow() && !UISettings.getInstance().getHideTabsIfNeeded() && myTabs.supportsCompression();
}
private void calculateCompressibleLengths(List<TabInfo> list, TablePassInfo data, int toFitLength) {
if (list.isEmpty()) return;
int hGap = myTabs.getTabHGap();
int maxGridSize = 0;
int spentLength = 0;
int lengthEstimation = 0;
int[] lengths = new int[list.size()];
for (int i = 0; i < list.size(); i++) {
TabInfo tabInfo = list.get(i);
final TabLabel label = myTabs.myInfo2Label.get(tabInfo);
if (maxGridSize == 0) {
Font font = label.getLabelComponent().getFont();
maxGridSize = GraphicsUtil.stringWidth("m", font == null ? JBFont.label() : font) * myTabs.tabMSize();
}
int lengthIncrement = Math.max(JBUI.scale(50), label.getPreferredSize().width);
lengths[i] = lengthIncrement;
lengthEstimation += lengthIncrement;
}
final int extraWidth = toFitLength - lengthEstimation;
Arrays.sort(lengths);
double acc = 0;
int actualGridSize = 0;
for (int i = 0; i < lengths.length; i++) {
int length = lengths[i];
acc += length;
actualGridSize = (int)Math.min(maxGridSize, (acc + extraWidth) / (i + 1));
if (i < lengths.length - 1 && actualGridSize < lengths[i + 1]) break;
}
for (Iterator<TabInfo> iterator = list.iterator(); iterator.hasNext(); ) {
TabInfo tabInfo = iterator.next();
final TabLabel label = myTabs.myInfo2Label.get(tabInfo);
int length;
int lengthIncrement = label.getPreferredSize().width;
if (!iterator.hasNext()) {
length = Math.min(toFitLength - spentLength, Math.max(actualGridSize, lengthIncrement));
}
else if (extraWidth <= 0) {//need compress
length = (int)(lengthIncrement * (float)toFitLength / lengthEstimation);
}
else {
length = Math.max(lengthIncrement, actualGridSize);
}
if (tabInfo.isPinned()) {
length = Math.min(getMaxPinnedTabWidth(), length);
}
length = Math.max(JBUI.scale(50), length);
data.lengths.put(tabInfo, length);
spentLength += length + hGap;
}
}
private void calculateRawLengths(List<TabInfo> list, TablePassInfo data) {
boolean showPinnedTabsSeparately = UISettings.getInstance().getState().getShowPinnedTabsInASeparateRow();
for (TabInfo info : list) {
TabLabel eachLabel = myTabs.getTabLabel(info);
Dimension size =
eachLabel.isPinned() && showPinnedTabsSeparately ? eachLabel.getNotStrictPreferredSize() : eachLabel.getPreferredSize();
data.lengths.put(info, Math.max(JBUI.scale(50), size.width + myTabs.getTabHGap()));
}
}
public LayoutPassInfo layoutTable(List<TabInfo> visibleInfos, JComponent title, JComponent moreToolbar) {
myTabs.resetLayout(true);
Rectangle unitedTabArea = null;
TablePassInfo data = computeLayoutTable(visibleInfos);
Rectangle rect = new Rectangle(data.moreRect);
rect.y += myTabs.getBorderThickness();
moreToolbar.setBounds(rect);
title.setBounds(data.titleRect);
Insets insets = myTabs.getLayoutInsets();
int eachY = insets.top;
for (TabInfo info : visibleInfos) {
Rectangle bounds = data.bounds.get(info);
if (unitedTabArea == null) {
unitedTabArea = bounds;
}
else {
unitedTabArea = unitedTabArea.union(bounds);
}
}
if (myTabs.getSelectedInfo() != null) {
final JBTabsImpl.Toolbar selectedToolbar = myTabs.myInfo2Toolbar.get(myTabs.getSelectedInfo());
final int componentY = eachY + (myTabs.isEditorTabs() ? 0 : 2) - myTabs.getLayoutInsets().top;
final int componentY = (unitedTabArea != null ? unitedTabArea.y + unitedTabArea.height : eachY) + (myTabs.isEditorTabs() ? 0 : 2) -
myTabs.getLayoutInsets().top;
if (!myTabs.myHorizontalSide && selectedToolbar != null && !selectedToolbar.isEmpty()) {
final int toolbarWidth = selectedToolbar.getPreferredSize().width;
final int vSeparatorWidth = toolbarWidth > 0 ? myTabs.getSeparatorWidth() : 0;
if (myTabs.isSideComponentBefore()) {
Rectangle compRect = myTabs.layoutComp(toolbarWidth + vSeparatorWidth, componentY, myTabs.getSelectedInfo().getComponent(), 0, 0);
Rectangle compRect =
myTabs.layoutComp(toolbarWidth + vSeparatorWidth, componentY, myTabs.getSelectedInfo().getComponent(), 0, 0);
myTabs.layout(selectedToolbar, compRect.x - toolbarWidth - vSeparatorWidth, compRect.y, toolbarWidth, compRect.height);
}
else {
@@ -175,7 +301,9 @@ public class TableLayout extends TabLayout {
myTabs.layoutComp(0, componentY, myTabs.getSelectedInfo().getComponent(), 0, 0);
}
}
if (unitedTabArea != null) {
data.tabRectangle.setBounds(unitedTabArea);
}
myLastTableLayout = data;
return data;
}
@@ -209,9 +337,9 @@ public class TableLayout extends TabLayout {
Rectangle secondBounds = second.getBounds();
final boolean between = firstBounds.getMaxX() < point.x
&& secondBounds.getX() > point.x
&& firstBounds.y < point.y
&& secondBounds.getMaxY() > point.y;
&& secondBounds.getX() > point.x
&& firstBounds.y < point.y
&& secondBounds.getMaxY() > point.y;
if (between) {
c = first;
@@ -232,7 +360,8 @@ public class TableLayout extends TabLayout {
}
}
result = index;
} else if (index < myLastTableLayout.myVisibleInfos.size()) {
}
else if (index < myLastTableLayout.myVisibleInfos.size()) {
result = index;
}
}
@@ -240,8 +369,111 @@ public class TableLayout extends TabLayout {
}
@Override
@MagicConstant(intValues = {SwingConstants.CENTER, SwingConstants.TOP, SwingConstants.LEFT, SwingConstants.BOTTOM, SwingConstants.RIGHT, -1})
@MagicConstant(intValues = {
SwingConstants.CENTER,
SwingConstants.TOP,
SwingConstants.LEFT,
SwingConstants.BOTTOM,
SwingConstants.RIGHT,
-1
})
public int getDropSideFor(@NotNull Point point) {
return TabsUtil.getDropSideFor(point, myTabs);
}
int getScrollOffset() {
return myScrollOffset;
}
public void scroll(int units) {
if (!myTabs.isSingleRow()) {
myScrollOffset = 0;
return;
}
myScrollOffset += units;
clampScrollOffsetToBounds(myLastTableLayout);
}
private void clampScrollOffsetToBounds(@Nullable TablePassInfo data) {
if (data == null) {
return;
}
if (data.requiredLength < data.toFitRec.width) {
myScrollOffset = 0;
}
else {
myScrollOffset = Math.max(0, Math.min(myScrollOffset,
data.requiredLength - data.toFitRec.width + data.moreRect.width /*+ (1 + myTabs.getIndexOf(myTabs.getSelectedInfo())) * myTabs.getBorderThickness()*/+ data.titleRect.width));
}
}
public int getScrollUnitIncrement() {
return 10;
}
public void scrollSelectionInView() {
myScrollSelectionInViewPending = true;
doScrollSelectionInView(myLastTableLayout);
}
@Override
public boolean canShowMorePopup() {
return myLastTableLayout != null && !myLastTableLayout.moreRect.isEmpty();
}
@Override
public void showMorePopup() {
Rectangle rect = myLastTableLayout != null ? myLastTableLayout.moreRect : null;
if (rect == null || rect.isEmpty()) return;
JBPopupMenu menu = new JBPopupMenu();
for (final TabInfo each : myLastTableLayout.invisible) {
menu.add(createMenuItem(each));
}
menu.show(myTabs, rect.x, rect.y + rect.height);
}
private JBMenuItem createMenuItem(@NotNull TabInfo tabInfo) {
final JBMenuItem item = new JBMenuItem(tabInfo.getText(), tabInfo.getIcon());
item.setForeground(tabInfo.getDefaultForeground());
item.setBackground(tabInfo.getTabColor());
item.addActionListener(__ -> myTabs.select(tabInfo, true));
return item;
}
private void doScrollSelectionInView(TablePassInfo data) {
if (myTabs.isMouseInsideTabsArea()
|| data == null
|| data.lengths.isEmpty()
|| myTabs.isHideTabs()
|| !UISettings.getInstance().getState().getShowPinnedTabsInASeparateRow()) {
return;
}
int offset = -myScrollOffset;
for (TabInfo info : data.myVisibleInfos) {
if (info.isPinned()) continue;
final int length = data.lengths.get(info);
if (info == myTabs.getSelectedInfo()) {
if (offset < 0) {
scroll(offset);
}
else {
final int maxLength = data.moreRect.x;
if (offset + length > maxLength) {
// left side should be always visible
if (length < maxLength) {
scroll(offset + length - maxLength);
}
else {
scroll(offset);
}
}
}
break;
}
offset += length;
}
}
}
@@ -4,22 +4,28 @@ package com.intellij.ui.tabs.impl.table;
import com.intellij.ui.tabs.TabInfo;
import com.intellij.ui.tabs.impl.JBTabsImpl;
import com.intellij.ui.tabs.impl.LayoutPassInfo;
import org.jetbrains.annotations.NotNull;
import java.awt.*;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.*;
class TablePassInfo extends LayoutPassInfo {
public class TablePassInfo extends LayoutPassInfo {
final List<TableRow> table = new ArrayList<>();
public final Rectangle toFitRec;
public final Rectangle tabRectangle = new Rectangle();
final Map<TabInfo, TableRow> myInfo2Row = new HashMap<>();
final JBTabsImpl myTabs;
@NotNull public final Rectangle moreRect = new Rectangle();
@NotNull public final Rectangle titleRect = new Rectangle();
public final List<TabInfo> invisible = new ArrayList<>();
final Map<TabInfo, Integer> lengths = new LinkedHashMap<>();
final Map<TabInfo, Rectangle> bounds = new HashMap<>();
int requiredLength = 0;
TablePassInfo(final JBTabsImpl tabs, List<TabInfo> visibleInfos) {
TablePassInfo(TableLayout layout, List<TabInfo> visibleInfos) {
super(visibleInfos);
myTabs = tabs;
myTabs = layout.myTabs;
final Insets insets = myTabs.getLayoutInsets();
toFitRec =
new Rectangle(insets.left, insets.top, myTabs.getWidth() - insets.left - insets.right, myTabs.getHeight() - insets.top - insets.bottom);
@@ -78,8 +78,7 @@ class EditorTabsConfigurable : BoundSearchableConfigurable(
myMultipleRowsRadio = radioButton(showTabsInMultipleRows)
.enableIf(myEditorTabPlacement.selectedValueIs(SwingConstants.TOP)).component
checkBox(showPinnedTabsInASeparateRow).enableIf(
myEditorTabPlacement.selectedValueIs(SwingConstants.TOP)
and myMultipleRowsRadio.selected).withLargeLeftGap().component
myEditorTabPlacement.selectedValueIs(SwingConstants.TOP)).withLargeLeftGap().component
})
}
val group = ButtonGroup()
@@ -654,6 +654,11 @@ public final class EditorTabbedContainer implements CloseAction.CloseTarget {
});
}
@Override
protected boolean supportsTableLayoutAsSingleRow() {
return true;
}
@Override
protected void paintChildren(Graphics g) {
super.paintChildren(g);