mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
JBR-2893 Big Sur: Add support of opening project as tabs
GitOrigin-RevId: 60da7d5975158ebade2345a360fa521cbd830426
This commit is contained in:
committed by
intellij-monorepo-bot
parent
ab86ebe099
commit
eecda53cd9
@@ -2,6 +2,7 @@
|
||||
package com.intellij.jdkEx;
|
||||
|
||||
import com.intellij.openapi.util.SystemInfo;
|
||||
import com.intellij.openapi.util.registry.Registry;
|
||||
import com.intellij.util.MethodInvocator;
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
@@ -121,4 +122,46 @@ public final class JdkEx {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static MethodInvocator mySetTabbingMode;
|
||||
|
||||
@Nullable
|
||||
private static MethodInvocator getTabbingModeInvocator() {
|
||||
if (!SystemInfo.isJetBrainsJvm || !SystemInfo.isMacOSBigSur || !Registry.is("ide.mac.bigsur.window.with.tabs.enabled", true)) {
|
||||
return null;
|
||||
}
|
||||
if (mySetTabbingMode == null) {
|
||||
try {
|
||||
mySetTabbingMode = new MethodInvocator(false, Class.forName("java.awt.Window"), "setTabbingMode");
|
||||
if (mySetTabbingMode.isAvailable()) {
|
||||
return mySetTabbingMode;
|
||||
}
|
||||
}
|
||||
catch (ClassNotFoundException ignore) {
|
||||
}
|
||||
return null;
|
||||
}
|
||||
return mySetTabbingMode.isAvailable() ? mySetTabbingMode : null;
|
||||
}
|
||||
|
||||
public static boolean isTabbingModeAvailable() {
|
||||
return getTabbingModeInvocator() != null;
|
||||
}
|
||||
|
||||
public static boolean setTabbingMode(@NotNull Window window, @Nullable Runnable moveTabToNewWindowCallback) {
|
||||
MethodInvocator invocator = getTabbingModeInvocator();
|
||||
if (invocator != null) {
|
||||
invocator.invoke(window);
|
||||
if (moveTabToNewWindowCallback != null) {
|
||||
try {
|
||||
new MethodInvocator(false, Class.forName("java.awt.Window"), "setMoveTabToNewWindowCallback", Runnable.class)
|
||||
.invoke(window, moveTabToNewWindowCallback);
|
||||
}
|
||||
catch (ClassNotFoundException ignore) {
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -67,6 +67,14 @@ public abstract class IdeFrameDecorator implements IdeFrameImpl.FrameDecorator {
|
||||
return null;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public static JComponent wrapRootPaneNorthSide(@NotNull JRootPane rootPane, @NotNull JComponent northComponent) {
|
||||
if (SystemInfo.isMac) {
|
||||
return MacMainFrameDecorator._wrapRootPaneNorthSide(rootPane, northComponent);
|
||||
}
|
||||
return northComponent;
|
||||
}
|
||||
|
||||
protected void notifyFrameComponents(boolean state) {
|
||||
myFrame.getRootPane().putClientProperty(FULL_SCREEN, state);
|
||||
JMenuBar menuBar = myFrame.getJMenuBar();
|
||||
|
||||
@@ -74,7 +74,7 @@ public class IdeRootPane extends JRootPane implements UISettingsListener {
|
||||
}
|
||||
}
|
||||
|
||||
myContentPane.add(myNorthPanel, BorderLayout.NORTH);
|
||||
myContentPane.add(IdeFrameDecorator.wrapRootPaneNorthSide(this, myNorthPanel), BorderLayout.NORTH);
|
||||
|
||||
// listen to mouse motion events for a11y
|
||||
myContentPane.addMouseMotionListener(new MouseMotionAdapter() {
|
||||
|
||||
@@ -3,22 +3,34 @@ package com.intellij.ui.mac;
|
||||
|
||||
import com.apple.eawt.*;
|
||||
import com.intellij.ide.ActiveWindowsWatcher;
|
||||
import com.intellij.jdkEx.JdkEx;
|
||||
import com.intellij.openapi.Disposable;
|
||||
import com.intellij.openapi.application.ApplicationManager;
|
||||
import com.intellij.openapi.diagnostic.Logger;
|
||||
import com.intellij.openapi.util.Disposer;
|
||||
import com.intellij.openapi.util.registry.Registry;
|
||||
import com.intellij.openapi.wm.IdeFrame;
|
||||
import com.intellij.openapi.wm.WindowManager;
|
||||
import com.intellij.openapi.wm.impl.IdeFrameDecorator;
|
||||
import com.intellij.openapi.wm.impl.IdeRootPane;
|
||||
import com.intellij.openapi.wm.impl.ProjectFrameHelper;
|
||||
import com.intellij.ui.components.panels.NonOpaquePanel;
|
||||
import com.intellij.ui.mac.foundation.Foundation;
|
||||
import com.intellij.ui.mac.foundation.ID;
|
||||
import com.intellij.ui.mac.foundation.MacUtil;
|
||||
import com.intellij.util.EventDispatcher;
|
||||
import com.intellij.util.ui.JBDimension;
|
||||
import com.intellij.util.ui.UIUtil;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.jetbrains.concurrency.AsyncPromise;
|
||||
import org.jetbrains.concurrency.Promise;
|
||||
import org.jetbrains.concurrency.Promises;
|
||||
|
||||
import javax.swing.*;
|
||||
import java.awt.*;
|
||||
import java.beans.PropertyChangeEvent;
|
||||
import java.beans.PropertyChangeListener;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.EventListener;
|
||||
import java.util.LinkedList;
|
||||
@@ -57,6 +69,8 @@ public final class MacMainFrameDecorator extends IdeFrameDecorator {
|
||||
myInFullScreen = true;
|
||||
storeFullScreenStateIfNeeded();
|
||||
myFullScreenQueue.runFromQueue();
|
||||
|
||||
updateTabBar(myFrame, 0);
|
||||
}
|
||||
|
||||
private void exitFullScreen() {
|
||||
@@ -66,6 +80,8 @@ public final class MacMainFrameDecorator extends IdeFrameDecorator {
|
||||
JRootPane rootPane = myFrame.getRootPane();
|
||||
if (rootPane != null) rootPane.putClientProperty(FULL_SCREEN, null);
|
||||
myFullScreenQueue.runFromQueue();
|
||||
|
||||
updateTabBarOnExitFromFullScreen();
|
||||
}
|
||||
|
||||
private void storeFullScreenStateIfNeeded() {
|
||||
@@ -104,9 +120,49 @@ public final class MacMainFrameDecorator extends IdeFrameDecorator {
|
||||
private final EventDispatcher<FSListener> myDispatcher = EventDispatcher.create(FSListener.class);
|
||||
private boolean myInFullScreen;
|
||||
|
||||
private static final String WIN_TAB_FILLER = "WIN_TAB_FILLER_KEY";
|
||||
|
||||
private static int DEFAULT_WIN_TAB_HEIGHT() {
|
||||
return Registry.intValue("ide.mac.bigsur.window.with.tabs.height", 28);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public static JComponent _wrapRootPaneNorthSide(@NotNull JRootPane rootPane, @NotNull JComponent northComponent) {
|
||||
if (!JdkEx.isTabbingModeAvailable()) {
|
||||
return northComponent;
|
||||
}
|
||||
|
||||
NonOpaquePanel panel = new NonOpaquePanel(new BorderLayout());
|
||||
|
||||
NonOpaquePanel filler = new NonOpaquePanel();
|
||||
filler.setVisible(false);
|
||||
|
||||
panel.add(filler, BorderLayout.NORTH);
|
||||
panel.add(northComponent);
|
||||
rootPane.putClientProperty(WIN_TAB_FILLER, filler);
|
||||
return panel;
|
||||
}
|
||||
|
||||
public MacMainFrameDecorator(@NotNull JFrame frame, @NotNull Disposable parentDisposable) {
|
||||
super(frame);
|
||||
|
||||
if (JdkEx.setTabbingMode(frame, () -> updateTabBars(null))) {
|
||||
// update tab logic only after call [NSWindow makeKeyAndOrderFront]
|
||||
frame.addPropertyChangeListener("focusableWindowState", new PropertyChangeListener() {
|
||||
@Override
|
||||
public void propertyChange(PropertyChangeEvent evt) {
|
||||
myFrame.removePropertyChangeListener("focusableWindowState", this);
|
||||
updateTabBars(myFrame);
|
||||
}
|
||||
});
|
||||
Disposer.register(parentDisposable, new Disposable() { // don't convert to lambda
|
||||
@Override
|
||||
public void dispose() {
|
||||
updateTabBars(null);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (leaveFullScreenMethod != null || toggleFullScreenMethod != null) {
|
||||
FullScreenUtilities.setWindowCanFullScreen(frame, true);
|
||||
|
||||
@@ -140,6 +196,7 @@ public final class MacMainFrameDecorator extends IdeFrameDecorator {
|
||||
if (rootPane != null && rootPane.getBorder() != null && Registry.is("ide.mac.transparentTitleBarAppearance")) {
|
||||
rootPane.setBorder(null);
|
||||
}
|
||||
updateTabBar(myFrame, 0);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -169,6 +226,115 @@ public final class MacMainFrameDecorator extends IdeFrameDecorator {
|
||||
}
|
||||
}
|
||||
|
||||
private static void updateTabBars(@Nullable JFrame newFrame) {
|
||||
if (!JdkEx.isTabbingModeAvailable()) {
|
||||
return;
|
||||
}
|
||||
|
||||
IdeFrame[] frames = WindowManager.getInstance().getAllProjectFrames();
|
||||
|
||||
if (frames.length < 2) {
|
||||
if (frames.length == 1) {
|
||||
updateTabBar(frames[0], 0);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
ApplicationManager.getApplication().invokeLater(() -> {
|
||||
Integer[] visibleAndHeights = new Integer[frames.length];
|
||||
boolean callInAppkit = false;
|
||||
int newIndex = -1;
|
||||
|
||||
for (int i = 0; i < frames.length; i++) {
|
||||
ProjectFrameHelper helper = (ProjectFrameHelper)frames[i];
|
||||
if (newFrame == helper.getFrame()) {
|
||||
//boolean visible = Foundation.invoke("NSWindow", "userTabbingPreference").intValue() == 1; // NSWindowUserTabbingPreferenceAlways = 1
|
||||
//visibleAndHeights[i] = visible ? DEFAULT_WIN_TAB_HEIGHT : 0;
|
||||
newIndex = i;
|
||||
}
|
||||
else if (helper.isInFullScreen()) {
|
||||
visibleAndHeights[i] = 0;
|
||||
}
|
||||
else {
|
||||
callInAppkit = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (callInAppkit) {
|
||||
// call only for shown window and only in Appkit
|
||||
Foundation.executeOnMainThread(true, false, () -> {
|
||||
for (int i = 0; i < frames.length; i++) {
|
||||
if (visibleAndHeights[i] == null) {
|
||||
ID window = MacUtil.getWindowFromJavaWindow(((ProjectFrameHelper)frames[i]).getFrame());
|
||||
visibleAndHeights[i] = (int)Foundation.invoke_fpret(window, "getTabBarVisibleAndHeight");
|
||||
if (visibleAndHeights[i] == -1) {
|
||||
visibleAndHeights[i] = DEFAULT_WIN_TAB_HEIGHT();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ApplicationManager.getApplication().invokeLater(() -> {
|
||||
for (int i = 0; i < frames.length; i++) {
|
||||
updateTabBar(frames[i], visibleAndHeights[i]);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
else {
|
||||
if (newIndex != -1) {
|
||||
visibleAndHeights[newIndex] = 0;
|
||||
}
|
||||
|
||||
for (int i = 0; i < frames.length; i++) {
|
||||
updateTabBar(frames[i], visibleAndHeights[i]);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void updateTabBarOnExitFromFullScreen() {
|
||||
if (!JdkEx.isTabbingModeAvailable()) {
|
||||
return;
|
||||
}
|
||||
|
||||
Foundation.executeOnMainThread(true, false, () -> {
|
||||
ID window = MacUtil.getWindowFromJavaWindow(myFrame);
|
||||
int visibleAndHeight = (int)Foundation.invoke_fpret(window, "getTabBarVisibleAndHeight");
|
||||
ApplicationManager.getApplication()
|
||||
.invokeLater(() -> updateTabBar(myFrame, visibleAndHeight == -1 ? DEFAULT_WIN_TAB_HEIGHT() : visibleAndHeight));
|
||||
});
|
||||
}
|
||||
|
||||
private static void updateTabBar(@NotNull Object frameObject, int height) {
|
||||
JFrame frame = null;
|
||||
if (frameObject instanceof JFrame) {
|
||||
frame = (JFrame)frameObject;
|
||||
}
|
||||
else if (frameObject instanceof ProjectFrameHelper) {
|
||||
frame = ((ProjectFrameHelper)frameObject).getFrame();
|
||||
}
|
||||
if (frame == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
JComponent filler = (JComponent)frame.getRootPane().getClientProperty(WIN_TAB_FILLER);
|
||||
if (filler == null) {
|
||||
return;
|
||||
}
|
||||
boolean visible = height > 0;
|
||||
boolean oldVisible = filler.isVisible();
|
||||
filler.setVisible(visible);
|
||||
filler.setPreferredSize(new JBDimension(-1, height));
|
||||
|
||||
Container parent = filler.getParent();
|
||||
if (parent == null || oldVisible == visible) {
|
||||
return;
|
||||
}
|
||||
parent.doLayout();
|
||||
parent.revalidate();
|
||||
parent.repaint();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isInFullScreen() {
|
||||
return myInFullScreen;
|
||||
|
||||
@@ -1916,4 +1916,11 @@ ide.new.stripes.ui=false
|
||||
ide.new.stripes.ui.description=One stripe layout
|
||||
|
||||
ide.web.preview.enabled=false
|
||||
ide.web.preview.enabled.description=Ability to preview html content directly in IDE
|
||||
ide.web.preview.enabled.description=Ability to preview html content directly in IDE
|
||||
|
||||
ide.mac.bigsur.window.with.tabs.height=28
|
||||
ide.mac.bigsur.window.with.tabs.height.description=Default height for tabs row
|
||||
|
||||
ide.mac.bigsur.window.with.tabs.enabled=true
|
||||
ide.mac.bigsur.window.with.tabs.enabled.restartRequired=true
|
||||
ide.mac.bigsur.window.with.tabs.enabled.description=Enable Mac OS Big Sur window tabs
|
||||
@@ -142,25 +142,43 @@ public final class MacUtil {
|
||||
Toolkit.getDefaultToolkit().addAWTEventListener(listener, AWTEvent.KEY_EVENT_MASK);
|
||||
}
|
||||
|
||||
public static ID findWindowFromJavaWindow(final Window w) {
|
||||
if (SystemInfo.isJetBrainsJvm && Registry.is("skip.untitled.windows.for.mac.messages")) {
|
||||
@NotNull
|
||||
public static ID getWindowFromJavaWindow(@Nullable Window w) {
|
||||
if (w == null) {
|
||||
return ID.NIL;
|
||||
}
|
||||
if (SystemInfo.isJetBrainsJvm) {
|
||||
try {
|
||||
Class<?> awtAccessor = Class.forName("sun.awt.AWTAccessor");
|
||||
Object componentAccessor = awtAccessor.getMethod("getComponentAccessor").invoke(null);
|
||||
Method getPeer = componentAccessor.getClass().getMethod("getPeer", Component.class);
|
||||
getPeer.setAccessible(true);
|
||||
Object peer = getPeer.invoke(componentAccessor, w);
|
||||
Class<?> cWindowPeerClass = peer.getClass();
|
||||
Method getPlatformWindowMethod = cWindowPeerClass.getDeclaredMethod("getPlatformWindow");
|
||||
Object cPlatformWindow = getPlatformWindowMethod.invoke(peer);
|
||||
Field ptr = cPlatformWindow.getClass().getSuperclass().getDeclaredField("ptr");
|
||||
ptr.setAccessible(true);
|
||||
return new ID(ptr.getLong(cPlatformWindow));
|
||||
if (peer != null) {
|
||||
Class<?> cWindowPeerClass = peer.getClass();
|
||||
Method getPlatformWindowMethod = cWindowPeerClass.getDeclaredMethod("getPlatformWindow");
|
||||
Object cPlatformWindow = getPlatformWindowMethod.invoke(peer);
|
||||
if (cPlatformWindow != null) {
|
||||
Field ptr = cPlatformWindow.getClass().getSuperclass().getDeclaredField("ptr");
|
||||
ptr.setAccessible(true);
|
||||
return new ID(ptr.getLong(cPlatformWindow));
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException | ClassNotFoundException | NoSuchFieldException e) {
|
||||
LOG.debug(e);
|
||||
}
|
||||
}
|
||||
return ID.NIL;
|
||||
}
|
||||
|
||||
public static ID findWindowFromJavaWindow(final Window w) {
|
||||
if (Registry.is("skip.untitled.windows.for.mac.messages")) {
|
||||
ID window = getWindowFromJavaWindow(w);
|
||||
if (!ID.NIL.equals(window)) {
|
||||
return window;
|
||||
}
|
||||
}
|
||||
return findWindowForTitle(getWindowTitle(w));
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user