Move Close Tabs menu item placement
Closed this issue · 10 comments
Hi. Thank you for reply. I have tried to do this from the beginning but without success. I will investigate it further when there will be some spare time.
I know this issue is pretty old, but as I have taken over this plugin I also tried to solve this, without any success as well. I only found out, that this is impossible by implementation as the WindowManagerImpl calls ActionUtils.createDefaultPopupActions and there the actions are instantiated and then added to a List of Actions and before the List is returned, there is a call for other actions from the Lookup and then the actions like "Close Right" and "Close Left" are loaded and added to that List. So there is no chance to get in between those actions before the current position.
The only possibility I see would be to provide a wrapper to the WindowManager and reorder the actions List, but manipulating the WindowManager in the IDE is not so easy and also not really a good idea.
I'll close this issue as there seems to be no solution to it.
I think it should be possible in theory by implementing org.netbeans.core.windows.actions.ActionsFactory
ServiceProvider that would reorder actions but as far as I can tell org.netbeans.core.windows.actions.ActionsFactory
is not a public api.
I was able to add dependency
<dependency>
<groupId>org.netbeans.modules</groupId>
<artifactId>org-netbeans-core-windows</artifactId>
<version>${netbeans.version}</version>
</dependency>
and
package de.funfried.netbeans.plugins.editor.closeleftright;
import org.netbeans.core.windows.actions.ActionsFactory;
import org.openide.util.lookup.ServiceProvider;
import org.openide.windows.Mode;
import org.openide.windows.TopComponent;
import javax.swing.*;
@ServiceProvider(service = ActionsFactory.class)
public class ReorderActionsFactory extends ActionsFactory {
public ReorderActionsFactory(){
System.out.println("ReorderActionsFactory");
}
@Override
public Action[] createPopupActions(TopComponent topComponent, Action[] actions) {
System.out.println("ReorderActionsFactory: createPopupActions topComponent");
return actions;
}
@Override
public Action[] createPopupActions(Mode mode, Action[] actions) {
System.out.println("ReorderActionsFactory: createPopupActions mode");
return actions;
}
}
but plugin loading fails with
de.funfried.netbeans.plugins.nb.editor.close.left.right [1.0.0 1.0.0 202205131217]
INFO [org.openide.util.lookup.MetaInfServicesLookup]
java.lang.ClassNotFoundException: org.netbeans.core.windows.actions.ActionsFactory
at java.net.URLClassLoader.findClass(URLClassLoader.java:387)
at java.lang.ClassLoader.loadClass(ClassLoader.java:418)
at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:352)
at java.lang.ClassLoader.loadClass(ClassLoader.java:351)
at org.netbeans.ProxyClassLoader.doFindClass(ProxyClassLoader.java:209)
Caused: java.lang.ClassNotFoundException: org.netbeans.core.windows.actions.ActionsFactory starting from ModuleCL@872da54[de.funfried.netbeans.plugins.nb.editor.close.left.right] with possible defining loaders [ModuleCL@77fd52ab[org.netbeans.core.windows]] and declared parents [ModuleCL@77fd52ab[org.netbeans.core.windows], ModuleCL@20ec0da6[org.openide.windows], ModuleCL@5631dad5[org.openide.text], org.netbeans.JarClassLoader@10429009]
at org.netbeans.ProxyClassLoader.doFindClass(ProxyClassLoader.java:211)
at org.netbeans.ProxyClassLoader.loadClass(ProxyClassLoader.java:125)
at java.lang.ClassLoader.loadClass(ClassLoader.java:351)
Caused: java.lang.NoClassDefFoundError: org/netbeans/core/windows/actions/ActionsFactory
at java.lang.ClassLoader.defineClass1(Native Method)
at java.lang.ClassLoader.defineClass(ClassLoader.java:756)
at org.netbeans.JarClassLoader.doLoadClass(JarClassLoader.java:287)
at org.netbeans.ProxyClassLoader.selfLoadClass(ProxyClassLoader.java:246)
Caused: java.lang.NoClassDefFoundError: org/netbeans/core/windows/actions/ActionsFactory while loading de.funfried.netbeans.plugins.editor.closeleftright.ReorderActionsFactory; see http://wiki.netbeans.org/DevFaqTroubleshootClassNotFound
at org.netbeans.ProxyClassLoader.selfLoadClass(ProxyClassLoader.java:250)
at org.netbeans.ProxyClassLoader.doFindClass(ProxyClassLoader.java:174)
at org.netbeans.ProxyClassLoader.loadClass(ProxyClassLoader.java:125)
at org.netbeans.ModuleManager$SystemClassLoader.loadClass(ModuleManager.java:769)
at java.lang.ClassLoader.loadClass(ClassLoader.java:351)
at java.lang.Class.forName0(Native Method)
at java.lang.Class.forName(Class.java:348)
at org.openide.util.lookup.MetaInfServicesLookup.search(MetaInfServicesLookup.java:306)
Caused: java.lang.ClassNotFoundException: org/netbeans/core/windows/actions/ActionsFactory while loading de.funfried.netbeans.plugins.editor.closeleftright.ReorderActionsFactory; see http://wiki.netbeans.org/DevFaqTroubleshootClassNotFound
[catch] at org.openide.util.lookup.MetaInfServicesLookup.search(MetaInfServicesLookup.java:311)
at org.openide.util.lookup.MetaInfServicesLookup.beforeLookup(MetaInfServicesLookup.java:131)
at org.openide.util.lookup.MetaInfServicesLookup.beforeLookupResult(MetaInfServicesLookup.java:110)
at org.openide.util.lookup.AbstractLookup.lookup(AbstractLookup.java:458)
at org.openide.util.lookup.ProxyLookup$R.initResults(ProxyLookup.java:449)
at org.openide.util.lookup.ProxyLookup$R.myBeforeLookup(ProxyLookup.java:736)
at org.openide.util.lookup.ProxyLookup$R.computeResult(ProxyLookup.java:612)
at org.openide.util.lookup.ProxyLookup$R.allInstances(ProxyLookup.java:572)
at org.openide.util.lookup.ProxyLookup$R.allInstances(ProxyLookup.java:568)
at org.openide.util.Lookup.lookupAll(Lookup.java:281)
at org.netbeans.core.windows.actions.ActionUtils.createDefaultPopupActions(ActionUtils.java:243)
at org.netbeans.core.windows.WindowManagerImpl.topComponentDefaultActions(WindowManagerImpl.java:1439)
at org.openide.windows.TopComponent.getActions(TopComponent.java:610)
at org.netbeans.core.multiview.MultiViewCloneableTopComponent.getActions(MultiViewCloneableTopComponent.java:153)
at org.netbeans.core.windows.view.ui.TabbedHandler.handlePopupMenuShowing(TabbedHandler.java:423)
at org.netbeans.core.windows.view.ui.TabbedHandler.actionPerformed(TabbedHandler.java:320)
at org.netbeans.swing.tabcontrol.TabbedContainer.postActionEvent(TabbedContainer.java:705)
at org.netbeans.swing.tabcontrol.TabbedContainerUI.shouldPerformAction(TabbedContainerUI.java:140)
at org.netbeans.swing.tabcontrol.plaf.DefaultTabbedContainerUI.access$2700(DefaultTabbedContainerUI.java:87)
at org.netbeans.swing.tabcontrol.plaf.DefaultTabbedContainerUI$DisplayerActionListener.actionPerformed(DefaultTabbedContainerUI.java:1261)
at org.netbeans.swing.tabcontrol.TabDisplayer.postActionEvent(TabDisplayer.java:589)
at org.netbeans.swing.tabcontrol.TabDisplayerUI.shouldPerformAction(TabDisplayerUI.java:168)
at org.netbeans.swing.tabcontrol.plaf.BasicTabDisplayerUI.access$1700(BasicTabDisplayerUI.java:96)
at org.netbeans.swing.tabcontrol.plaf.BasicTabDisplayerUI$BasicDisplayerMouseListener.performCommand(BasicTabDisplayerUI.java:760)
at org.netbeans.swing.tabcontrol.plaf.BasicTabDisplayerUI$BasicDisplayerMouseListener.potentialCommand(BasicTabDisplayerUI.java:736)
at org.netbeans.swing.tabcontrol.plaf.BasicTabDisplayerUI$BasicDisplayerMouseListener.mousePressed(BasicTabDisplayerUI.java:720)
at java.awt.AWTEventMulticaster.mousePressed(AWTEventMulticaster.java:279)
at java.awt.AWTEventMulticaster.mousePressed(AWTEventMulticaster.java:279)
at java.awt.Component.processMouseEvent(Component.java:6536)
at javax.swing.JComponent.processMouseEvent(JComponent.java:3324)
at java.awt.Component.processEvent(Component.java:6304)
at java.awt.Container.processEvent(Container.java:2239)
at java.awt.Component.dispatchEventImpl(Component.java:4889)
at java.awt.Container.dispatchEventImpl(Container.java:2297)
at java.awt.Component.dispatchEvent(Component.java:4711)
at java.awt.LightweightDispatcher.retargetMouseEvent(Container.java:4904)
at java.awt.LightweightDispatcher.processMouseEvent(Container.java:4532)
at java.awt.LightweightDispatcher.dispatchEvent(Container.java:4476)
at java.awt.Container.dispatchEventImpl(Container.java:2283)
at java.awt.Window.dispatchEventImpl(Window.java:2746)
at java.awt.Component.dispatchEvent(Component.java:4711)
at java.awt.EventQueue.dispatchEventImpl(EventQueue.java:760)
at java.awt.EventQueue.access$500(EventQueue.java:97)
at java.awt.EventQueue$3.run(EventQueue.java:709)
at java.awt.EventQueue$3.run(EventQueue.java:703)
at java.security.AccessController.doPrivileged(Native Method)
at java.security.ProtectionDomain$JavaSecurityAccessImpl.doIntersectionPrivilege(ProtectionDomain.java:74)
at java.security.ProtectionDomain$JavaSecurityAccessImpl.doIntersectionPrivilege(ProtectionDomain.java:84)
at java.awt.EventQueue$4.run(EventQueue.java:733)
at java.awt.EventQueue$4.run(EventQueue.java:731)
at java.security.AccessController.doPrivileged(Native Method)
at java.security.ProtectionDomain$JavaSecurityAccessImpl.doIntersectionPrivilege(ProtectionDomain.java:74)
at java.awt.EventQueue.dispatchEvent(EventQueue.java:730)
at org.netbeans.core.TimableEventQueue.dispatchEvent(TimableEventQueue.java:136)
at java.awt.EventDispatchThread.pumpOneEventForFilters(EventDispatchThread.java:205)
at java.awt.EventDispatchThread.pumpEventsForFilter(EventDispatchThread.java:116)
at java.awt.EventDispatchThread.pumpEventsForHierarchy(EventDispatchThread.java:105)
at java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:101)
at java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:93)
at java.awt.EventDispatchThread.run(EventDispatchThread.java:82)
WindowManagerImpl calls ActionUtils.createDefaultPopupActions and there the actions are instantiated and then added to a List of Actions and before the List is returned, there is a call for other actions from the Lookup and then the actions like "Close Right" and "Close Left" are loaded and added to that List.
Indeed, the org.netbeans.core.windows.actions.ActionsFactory
only observes a list of actions before other actions from Lookup are loaded. I guess it could be possible to instantiate and insert close left/right actions here instead of adding them via @ActionRegistration
but as mentioned before ActionsFactory
is not a public API and to use it plugin would need to pin the implementation version like so
diff --git a/pom.xml b/pom.xml
index 062794b..232408c 100644
--- a/pom.xml
+++ b/pom.xml
@@ -117,6 +117,13 @@
<artifactId>org-openide-windows</artifactId>
<version>${netbeans.version}</version>
</dependency>
+
+ <dependency>
+ <groupId>org.netbeans.modules</groupId>
+ <artifactId>org-netbeans-core-windows</artifactId>
+ <version>${netbeans.version}</version>
+ </dependency>
+
</dependencies>
<build>
@@ -126,6 +133,16 @@
<groupId>org.apache.netbeans.utilities</groupId>
<artifactId>nbm-maven-plugin</artifactId>
<version>4.7</version>
+ <configuration>
+ <moduleDependencies>
+ <dependency>
+ <id>org.netbeans.modules:org-netbeans-core-windows</id>
+ <type>impl</type>
+ <explicitValue>org.netbeans.core.windows/2 = 13-00d6d969bf4d9b14e7406c9ee9cc13a61dc39655</explicitValue>
+ </dependency>
+ </moduleDependencies>
+ </configuration>
+
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
and the close left/right implementation would most likely be different.
@AlexanderYastrebov Thanks for the hints, I'll definitely will look deeper into this, could be a solution indeed. But I guess I could only support one NetBeans IDE version like NetBeans IDE 13 (or 12 or 11, but not e.g. NetBeans IDE 11+) because of the direct explicit dependency.
I was able to implement it, thanks to Yenta which can befriend a restricted module with your own plugin. Unfortunately, Yenta is only available as a NetBeans plugin (nbm) in Maven central and not as a jar (yet), so if I add Yenta as a dependency it is not packed inside the plugin as e.g. commons-lang, NetBeans will simple assume it will be available in the user's IDE like e.g. the org-openide-windows nbm, but that's not the case, so a user would need to install it manually and actually it should be installed beforehand, otherwise the nb-editor-close-left-right plugin will be installed deactivated and the user would need to manually enable it after he/she installed the Yenta plugin. That would not be the case if I would have it as a jar or the plugin is available in the NetBeans plugin portal, both options are checked right now by the developer of Yenta and as soon as one of the two options is available, I will switch to Yenta as a dependency, since then I guess I have to keep the one source file inside the nb-editor-close-left-right plugin.
Here's the Yenta issue: jglick/yenta#2
@funfried Nice!
I think it is fine to vendor-in Yenta.java
like you did and have no dependency on it.
I would not pull commons-lang just for one function though:
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
class Scratch {
public static void main(String[] args) {
String[] actions = new String[]{"Close", "Close All", "Close Other", "---", "Maximize", "Float"};
int insertAt = actions.length > 3 ? 3 : actions.length;
List<String> actionList = Arrays.asList(actions);
List<String> result = new ArrayList<>();
result.addAll(actionList.subList(0, insertAt));
result.add("Close Left");
result.add("Close Right");
result.addAll(actionList.subList(insertAt, actionList.size()));
actions = result.toArray(new String[0]);
System.out.println(String.join("\n", actions));
}
}
Good point (commons-lang), but I don't think it should be a problem (it has a size of 574K) and your code snippet would work, but as the ActionFactory is taken into account for just any context menu in the whole IDE, it would be shown everywhere and no matter if a TopComponent can be closed or not, that's the reason why I check for the other close other actions, so I won't add close left and right and something cannot be closed (not sure when that's the case, but at least TopComponents can be closable a not closable)
Up to you, I think there are two createPopupActions
in ActionsFactory
but only one (receiving the Mode) is called for editor panes. I think it may also be possible to check the that mode==editor and insert actions only for it https://netbeans.apache.org/wiki/DevFaqWindowsMode.asciidoc
Should be fixed in latest SNAPSHOT release and will be published in a final released soon