mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
IDEA-253426 Implement "Show pinned tabs in a separate row" for single row layout
GitOrigin-RevId: 11ccc9b7f90ec47ed518230713e23be5b8f1dfec
This commit is contained in:
committed by
intellij-monorepo-bot
parent
d06b9eb6cb
commit
1418aaa7fb
@@ -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);
|
||||
|
||||
+1
-2
@@ -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()
|
||||
|
||||
+5
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user