From 0b20dd75d2f993ef1e25aff7f08bc59c3ff89aaa Mon Sep 17 00:00:00 2001 From: Neil C Smith Date: Thu, 5 Mar 2026 10:55:00 +0000 Subject: [PATCH] Add Locate in System action to open folder of file. Replace macOS specific Show in Finder action with an action available on all OS to open the parent folder of any file. --- .../modules/applemenu/Bundle.properties | 3 - .../modules/applemenu/ShowInFinder.java | 99 -------------- .../core/ui/sysopen/Bundle.properties | 1 + .../core/ui/sysopen/SystemLocateAction.java | 126 ++++++++++++++++++ 4 files changed, 127 insertions(+), 102 deletions(-) delete mode 100644 platform/applemenu/src/org/netbeans/modules/applemenu/ShowInFinder.java create mode 100644 platform/core.ui/src/org/netbeans/core/ui/sysopen/SystemLocateAction.java diff --git a/platform/applemenu/src/org/netbeans/modules/applemenu/Bundle.properties b/platform/applemenu/src/org/netbeans/modules/applemenu/Bundle.properties index 67d980400859..89a205979778 100644 --- a/platform/applemenu/src/org/netbeans/modules/applemenu/Bundle.properties +++ b/platform/applemenu/src/org/netbeans/modules/applemenu/Bundle.properties @@ -26,6 +26,3 @@ OpenIDE-Module-Long-Description=Enables Apple menu items to work properly, \ Toolbars/QuickSearch=&Quick Search - -CTL_ShowInFinder=Show In Finder -TXT_NoLocalFile=Cannot find selected file. diff --git a/platform/applemenu/src/org/netbeans/modules/applemenu/ShowInFinder.java b/platform/applemenu/src/org/netbeans/modules/applemenu/ShowInFinder.java deleted file mode 100644 index ee4ad67d8793..000000000000 --- a/platform/applemenu/src/org/netbeans/modules/applemenu/ShowInFinder.java +++ /dev/null @@ -1,99 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -package org.netbeans.modules.applemenu; - -import java.awt.event.ActionListener; -import java.awt.event.ActionEvent; -import java.io.File; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.util.logging.Level; -import java.util.logging.Logger; -import org.openide.loaders.DataObject; - -import org.openide.awt.ActionRegistration; -import org.openide.awt.ActionReference; -import org.openide.awt.ActionReferences; -import org.openide.awt.ActionID; -import org.openide.awt.StatusDisplayer; -import org.openide.filesystems.FileObject; -import org.openide.filesystems.FileUtil; -import org.openide.util.NbBundle; -import org.openide.util.RequestProcessor; - -/** - * - * @author Tomas Zezula - */ -@ActionID(category = "Tools", -id = "org.netbeans.modules.applemenu.ShowInFinder") -@ActionRegistration(displayName = "#CTL_ShowInFinder") -@ActionReferences({ - @ActionReference(path = "UI/ToolActions/Files", position = 1001) -}) -public final class ShowInFinder implements ActionListener { - - private static final Logger LOG = Logger.getLogger(ShowInFinder.class.getName()); - - private final DataObject context; - - private static final RequestProcessor RP = new RequestProcessor( "ShowInFinder", 1 ); - - public ShowInFinder(DataObject context) { - this.context = context; - } - - @Override - public void actionPerformed(ActionEvent ev) { - RP.execute(new Runnable() { - @Override - public void run() { - FileObject fobj = context.getPrimaryFile(); - if (fobj == null) { - return; - } - LOG.log(Level.FINE, "Selected file: {0}", FileUtil.getFileDisplayName(fobj)); //NOI18N - if (FileUtil.getArchiveFile(fobj)!=null) { - fobj = FileUtil.getArchiveFile(fobj); - } - LOG.log(Level.FINE, "File to select in Finder: {0}", FileUtil.getFileDisplayName(fobj)); //NOI18N - final File file = FileUtil.toFile(fobj); - if (file == null) { - LOG.log(Level.INFO, "Ignoring non local file: {0}", FileUtil.getFileDisplayName(fobj)); //NOI18N - StatusDisplayer.getDefault().setStatusText(NbBundle.getMessage(ShowInFinder.class, "TXT_NoLocalFile")); //NOI18N - return; - } - try { - final Class fmClz = Class.forName("com.apple.eio.FileManager"); //NOI18N - final Method m = fmClz.getDeclaredMethod("revealInFinder", File.class); //NOI18N - m.invoke(null, file); - } catch (ClassNotFoundException e) { - LOG.log(Level.FINE, "Cannot load com.apple.eio.FileManager class."); //NOI18N - } catch (NoSuchMethodException e) { - LOG.log(Level.FINE, "No method revealInFinder(java.io.File) in the com.apple.eio.FileManager class."); //NOI18N - } catch (InvocationTargetException e) { - LOG.log(Level.FINE, "Cannot invoke method com.apple.eio.FileManager.revealInFinder(java.io.File)."); //NOI18N - } catch (IllegalAccessException e) { - LOG.log(Level.FINE, "The method com.apple.eio.FileManager.revealInFinder(java.io.File) is not accessible"); //NOI18N - } - } - }); - } -} diff --git a/platform/core.ui/src/org/netbeans/core/ui/sysopen/Bundle.properties b/platform/core.ui/src/org/netbeans/core/ui/sysopen/Bundle.properties index d33f167a2d8d..d1fc95c81886 100644 --- a/platform/core.ui/src/org/netbeans/core/ui/sysopen/Bundle.properties +++ b/platform/core.ui/src/org/netbeans/core/ui/sysopen/Bundle.properties @@ -16,3 +16,4 @@ # under the License. CTL_SystemOpenAction=Open in System +CTL_SystemLocateAction=Locate in System diff --git a/platform/core.ui/src/org/netbeans/core/ui/sysopen/SystemLocateAction.java b/platform/core.ui/src/org/netbeans/core/ui/sysopen/SystemLocateAction.java new file mode 100644 index 000000000000..9dedc5b84c7f --- /dev/null +++ b/platform/core.ui/src/org/netbeans/core/ui/sysopen/SystemLocateAction.java @@ -0,0 +1,126 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.netbeans.core.ui.sysopen; + +import java.awt.Desktop; +import java.awt.event.ActionEvent; +import java.util.Collection; +import java.util.logging.Level; +import java.util.logging.Logger; +import javax.swing.AbstractAction; +import javax.swing.Action; +import org.openide.awt.ActionID; +import org.openide.awt.ActionReference; +import org.openide.awt.ActionReferences; +import org.openide.awt.ActionRegistration; +import org.openide.awt.DynamicMenuContent; +import org.openide.filesystems.FileObject; +import org.openide.filesystems.FileUtil; +import org.openide.loaders.DataObject; +import org.openide.util.ContextAwareAction; +import org.openide.util.Lookup; +import org.openide.util.LookupListener; +import org.openide.util.NbBundle; +import org.openide.util.RequestProcessor; +import org.openide.util.Utilities; +import org.openide.util.WeakListeners; + +/** + * Open the selected file(s) with the system default tool. + * + * @author Jesse Glick + */ +@ActionID(id = "org.netbeans.core.ui.sysopen.SystemLocateAction", category = "Edit") +@ActionRegistration(displayName = "#CTL_SystemLocateAction", lazy = false) +@ActionReferences({ + @ActionReference(path = "Editors/TabActions", position = 402), + @ActionReference(path = "UI/ToolActions/Files", position = 2046), + @ActionReference(path = "Projects/Actions", position = 102), + @ActionReference(path = "Shortcuts", name = "DO-S") +}) +public final class SystemLocateAction extends AbstractAction implements ContextAwareAction { + + private static final RequestProcessor PROC = new RequestProcessor(SystemLocateAction.class); + + private final Lookup.Result result; + private final LookupListener resultListener; + + private FileObject file; + + public SystemLocateAction() { + this(Utilities.actionsGlobalContext()); + } + + private SystemLocateAction(Lookup context) { + super(NbBundle.getMessage(SystemLocateAction.class, "CTL_SystemLocateAction")); + putValue(DynamicMenuContent.HIDE_WHEN_DISABLED, true); + result = context.lookupResult(DataObject.class); + resultListener = e -> updateFile(); + result.addLookupListener(WeakListeners.create(LookupListener.class, resultListener, result)); + updateFile(); + } + + @Override + public void actionPerformed(ActionEvent e) { + FileObject fo = file; + if (fo == null) { + return; + } + PROC.post(() -> { // #176879: asynch + Desktop desktop = Desktop.getDesktop(); + try { + if (desktop.isSupported(Desktop.Action.BROWSE_FILE_DIR)) { + desktop.browseFileDirectory(FileUtil.toFile(fo)); + } else { + desktop.open(FileUtil.toFile(fo.getParent())); + } + } catch (Exception x) { + Logger.getLogger(SystemLocateAction.class.getName()).log(Level.INFO, null, x); + // XXX or perhaps notify user of problem; but not very useful on Unix at least (#6940853) + } + }); + } + + @Override + public Action createContextAwareInstance(Lookup context) { + return new SystemLocateAction(context); + } + + @Override + public boolean isEnabled() { + return file != null && Desktop.isDesktopSupported() + && (Desktop.getDesktop().isSupported(Desktop.Action.BROWSE_FILE_DIR) + || Desktop.getDesktop().isSupported(Desktop.Action.OPEN)); + } + + private void updateFile() { + Collection dobs = result.allInstances(); + if (dobs.size() == 1) { + FileObject fo = dobs.iterator().next().getPrimaryFile(); + if (fo.isData()) { + this.file = fo; + } else { + this.file = null; + } + } else { + file = null; + } + } + +}