From 737a41f0870595e9c5456cccd5348c3c241ddaa4 Mon Sep 17 00:00:00 2001 From: Jaroslav Tulach Date: Mon, 21 Sep 2026 15:26:26 +0200 Subject: [PATCH 1/4] More prominent Open Folder (as a group) action --- .../modules/project/ui/groups/AdHocGroup.java | 1 - .../ui/groups/DirectoryGroupEditPanel.java | 17 ++--- .../ui/groups/OpenFolderAsGroupAction.java | 69 +++++++++++++++++++ 3 files changed, 73 insertions(+), 14 deletions(-) create mode 100644 ide/projectui/src/org/netbeans/modules/project/ui/groups/OpenFolderAsGroupAction.java diff --git a/ide/projectui/src/org/netbeans/modules/project/ui/groups/AdHocGroup.java b/ide/projectui/src/org/netbeans/modules/project/ui/groups/AdHocGroup.java index a58a5d1dfcac..f940fa4fe3c5 100644 --- a/ide/projectui/src/org/netbeans/modules/project/ui/groups/AdHocGroup.java +++ b/ide/projectui/src/org/netbeans/modules/project/ui/groups/AdHocGroup.java @@ -20,7 +20,6 @@ package org.netbeans.modules.project.ui.groups; import java.util.Arrays; -import java.util.Collection; import java.util.Collections; import java.util.HashSet; import java.util.List; diff --git a/ide/projectui/src/org/netbeans/modules/project/ui/groups/DirectoryGroupEditPanel.java b/ide/projectui/src/org/netbeans/modules/project/ui/groups/DirectoryGroupEditPanel.java index 039d04aa4242..4923449e7b94 100644 --- a/ide/projectui/src/org/netbeans/modules/project/ui/groups/DirectoryGroupEditPanel.java +++ b/ide/projectui/src/org/netbeans/modules/project/ui/groups/DirectoryGroupEditPanel.java @@ -19,6 +19,7 @@ package org.netbeans.modules.project.ui.groups; +import java.awt.HeadlessException; import java.io.File; import java.util.prefs.Preferences; import javax.swing.JFileChooser; @@ -160,19 +161,9 @@ public void actionPerformed(java.awt.event.ActionEvent evt) { }// //GEN-END:initComponents private void directoryButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_directoryButtonActionPerformed - JFileChooser chooser = new JFileChooser(); - chooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY); - chooser.setMultiSelectionEnabled(false); - File start = ProjectChooser.getProjectsFolder(); - if (folderField.getText() != null && folderField.getText().trim().length() > 0) { - start = new File(folderField.getText().trim()); - } - chooser.setCurrentDirectory(start); - if (chooser.showOpenDialog(this) == JFileChooser.APPROVE_OPTION) { - File f = chooser.getSelectedFile(); - if (f != null) { - folderField.setText(f.getAbsolutePath()); - } + File f = OpenFolderAsGroupAction.showWorkspaceFolderChooser(this, folderField.getText()); + if (f != null) { + folderField.setText(f.getAbsolutePath()); } }//GEN-LAST:event_directoryButtonActionPerformed diff --git a/ide/projectui/src/org/netbeans/modules/project/ui/groups/OpenFolderAsGroupAction.java b/ide/projectui/src/org/netbeans/modules/project/ui/groups/OpenFolderAsGroupAction.java new file mode 100644 index 000000000000..c8d94cbfc9df --- /dev/null +++ b/ide/projectui/src/org/netbeans/modules/project/ui/groups/OpenFolderAsGroupAction.java @@ -0,0 +1,69 @@ +/* + * 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.project.ui.groups; + +import java.awt.HeadlessException; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.io.File; +import javax.swing.JFileChooser; +import org.netbeans.spi.project.ui.support.ProjectChooser; +import org.openide.awt.ActionID; +import org.openide.awt.ActionReference; +import org.openide.awt.ActionRegistration; +import org.openide.filesystems.FileObject; +import org.openide.filesystems.FileUtil; +import org.openide.util.NbBundle.Messages; + +@ActionID( + category = "Project", + id = "org.netbeans.modules.project.ui.groups.OpenFolderAsGroupAction" +) +@ActionRegistration( + displayName = "#CTL_OpenFolderAsGroupAction", + lazy = true, asynchronous = true +) +@ActionReference(path = "Menu/File", position = 1200, separatorAfter = 1250) +@Messages("CTL_OpenFolderAsGroupAction=Open Fol&der as Workspace...") +public final class OpenFolderAsGroupAction implements ActionListener { + @Override + public void actionPerformed(ActionEvent ev) { + File dir = showWorkspaceFolderChooser(null, null); + FileObject folder = FileUtil.toFileObject(dir); + if (folder != null) { + DirectoryGroup group = DirectoryGroup.create(folder.getNameExt(), folder); + Group.setActiveGroup(group, true); + } + } + + static File showWorkspaceFolderChooser(java.awt.Component parent, final String hintPath) throws HeadlessException { + JFileChooser chooser = new JFileChooser(); + chooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY); + chooser.setMultiSelectionEnabled(false); + File start = ProjectChooser.getProjectsFolder(); + if (hintPath != null && hintPath.trim().length() > 0) { + start = new File(hintPath.trim()); + } + chooser.setCurrentDirectory(start); + final int result = chooser.showOpenDialog(parent); + File f = result == JFileChooser.APPROVE_OPTION ? chooser.getSelectedFile() : null; + return f; + } + +} From 93db08e3a7f19e8e0bcb8a5c01843f72920e2f7c Mon Sep 17 00:00:00 2001 From: Jaroslav Tulach Date: Tue, 22 Sep 2026 10:54:31 +0200 Subject: [PATCH 2/4] Rebranding Project Groups to Workspace --- ide/projectui/apichanges.xml | 2 +- ide/projectui/arch.xml | 4 +- .../modules/project/ui/Bundle.properties | 2 +- .../project/ui/OpenProjectListSettings.java | 2 +- .../project/ui/groups/Bundle.properties | 44 +++++++++---------- .../modules/project/ui/groups/Group.java | 4 +- .../ui/groups/GroupOptionProcessor.java | 6 +-- .../modules/project/ui/groups/GroupsMenu.java | 14 +++--- .../project/ui/groups/NewGroupPanel.java | 2 +- 9 files changed, 40 insertions(+), 40 deletions(-) diff --git a/ide/projectui/apichanges.xml b/ide/projectui/apichanges.xml index fdacf3f9e48c..aec1c9e24fd6 100644 --- a/ide/projectui/apichanges.xml +++ b/ide/projectui/apichanges.xml @@ -70,7 +70,7 @@

@ProjectCustomizer.CompositeCategoryProvider.Registration annotation now accepts "Groups" projectType parameter value which - adds a panel to the Project Group customizer. Implementations can expect an instance of org.netbeans.api.project.ui.ProjectGroup in the lookup + adds a panel to the workspace customizer. Implementations can expect an instance of org.netbeans.api.project.ui.ProjectGroup in the lookup passed to the provider. NO central storing procedure in place, each panel is expected to set it's own values in Category.setStoreListener

diff --git a/ide/projectui/arch.xml b/ide/projectui/arch.xml index 9215ba685eee..46a9a8c7c67b 100644 --- a/ide/projectui/arch.xml +++ b/ide/projectui/arch.xml @@ -623,7 +623,7 @@
  • - Can use --open-group NAME to open a project group at startup, or --close-group. + Can use --open-group NAME to open a workspace at startup, or --close-group.

  • @@ -1155,7 +1155,7 @@

    - Project groups. See Group. + Workspaces. See Group.

    diff --git a/ide/projectui/src/org/netbeans/modules/project/ui/Bundle.properties b/ide/projectui/src/org/netbeans/modules/project/ui/Bundle.properties index a2b923f28664..84822327978e 100644 --- a/ide/projectui/src/org/netbeans/modules/project/ui/Bundle.properties +++ b/ide/projectui/src/org/netbeans/modules/project/ui/Bundle.properties @@ -50,7 +50,7 @@ LBL_ProjectMode=Projects #ExportOptions Export_Category_Display_Name=Projects Export_Display_Name=Basic -Export_Groups_Display_Name=Project Groups +Export_Groups_Display_Name=Workspaces #Nodes LBL_OpenProjectsNode_Name=Projects diff --git a/ide/projectui/src/org/netbeans/modules/project/ui/OpenProjectListSettings.java b/ide/projectui/src/org/netbeans/modules/project/ui/OpenProjectListSettings.java index fa602d15f00f..87a0189679ee 100644 --- a/ide/projectui/src/org/netbeans/modules/project/ui/OpenProjectListSettings.java +++ b/ide/projectui/src/org/netbeans/modules/project/ui/OpenProjectListSettings.java @@ -231,7 +231,7 @@ public void setOpenProjectsIcons( List list ) { } public boolean isOpenSubprojects() { - return getPreferences().getBoolean( OPEN_SUBPROJECTS, false); + return getPreferences().getBoolean( OPEN_SUBPROJECTS, true); } public void setOpenSubprojects( boolean openSubprojects ) { diff --git a/ide/projectui/src/org/netbeans/modules/project/ui/groups/Bundle.properties b/ide/projectui/src/org/netbeans/modules/project/ui/groups/Bundle.properties index 9d426f8d6511..9b54d855d7cd 100644 --- a/ide/projectui/src/org/netbeans/modules/project/ui/groups/Bundle.properties +++ b/ide/projectui/src/org/netbeans/modules/project/ui/groups/Bundle.properties @@ -36,35 +36,35 @@ SubprojectsGroupEditPanel.masterProjectLabel.text=&Master Project: DirectoryGroupEditPanel.nameLabel.text=&Name\: DirectoryGroupEditPanel.folderLabel.text=&Folder\: -SubprojectsGroupEditPanel.nameLabel.AccessibleContext.accessibleDescription=Name of the project group +SubprojectsGroupEditPanel.nameLabel.AccessibleContext.accessibleDescription=Name of the workspace SubprojectsGroupEditPanel.masterProjectLabel.AccessibleContext.accessibleDescription=Group master project name SubprojectsGroupEditPanel.nameField.AccessibleContext.accessibleName=Name -SubprojectsGroupEditPanel.nameField.AccessibleContext.accessibleDescription=Project group name +SubprojectsGroupEditPanel.nameField.AccessibleContext.accessibleDescription=Workspace name SubprojectsGroupEditPanel.masterProjectField.AccessibleContext.accessibleName= SubprojectsGroupEditPanel.masterProjectField.AccessibleContext.accessibleDescription= -SubprojectsGroupEditPanel.AccessibleContext.accessibleName=Project group properties -SubprojectsGroupEditPanel.AccessibleContext.accessibleDescription=Project group properties -DirectoryGroupEditPanel.nameLabel.AccessibleContext.accessibleDescription=Name of the project group -DirectoryGroupEditPanel.folderLabel.AccessibleContext.accessibleDescription=Project group folder +SubprojectsGroupEditPanel.AccessibleContext.accessibleName=Workspace properties +SubprojectsGroupEditPanel.AccessibleContext.accessibleDescription=Workspace properties +DirectoryGroupEditPanel.nameLabel.AccessibleContext.accessibleDescription=Name of the workspace +DirectoryGroupEditPanel.folderLabel.AccessibleContext.accessibleDescription=Workspace folder DirectoryGroupEditPanel.nameField.AccessibleContext.accessibleName=Name -DirectoryGroupEditPanel.nameField.AccessibleContext.accessibleDescription=Project group name +DirectoryGroupEditPanel.nameField.AccessibleContext.accessibleDescription=Workspace name DirectoryGroupEditPanel.folderField.AccessibleContext.accessibleName= DirectoryGroupEditPanel.folderField.AccessibleContext.accessibleDescription= -DirectoryGroupEditPanel.AccessibleContext.accessibleName=Project group properties -DirectoryGroupEditPanel.AccessibleContext.accessibleDescription=Project group properties -AdHocGroupEditPanel.AccessibleContext.accessibleName=Project group properties -AdHocGroupEditPanel.AccessibleContext.accessibleDescription=Project group properties -AdHocGroupEditPanel.nameLabel.AccessibleContext.accessibleDescription=Project group name +DirectoryGroupEditPanel.AccessibleContext.accessibleName=Workspace properties +DirectoryGroupEditPanel.AccessibleContext.accessibleDescription=Workspace properties +AdHocGroupEditPanel.AccessibleContext.accessibleName=Workspace properties +AdHocGroupEditPanel.AccessibleContext.accessibleDescription=Workspace properties +AdHocGroupEditPanel.nameLabel.AccessibleContext.accessibleDescription=Workspace name AdHocGroupEditPanel.nameField.AccessibleContext.accessibleName=Name -AdHocGroupEditPanel.nameField.AccessibleContext.accessibleDescription=Project group name -AdHocGroupEditPanel.autoSynchCheckbox.AccessibleContext.accessibleDescription=Automatically save project group list +AdHocGroupEditPanel.nameField.AccessibleContext.accessibleDescription=Workspace name +AdHocGroupEditPanel.autoSynchCheckbox.AccessibleContext.accessibleDescription=Automatically save workspace list AdHocGroupEditPanel.synchButton.AccessibleContext.accessibleDescription=Save current project list now -NewGroupPanel.AccessibleContext.accessibleName=New project group -NewGroupPanel.AccessibleContext.accessibleDescription=New project group -NewGroupPanel.nameLabel.AccessibleContext.accessibleDescription=Group name +NewGroupPanel.AccessibleContext.accessibleName=New workspace +NewGroupPanel.AccessibleContext.accessibleDescription=New workspace +NewGroupPanel.nameLabel.AccessibleContext.accessibleDescription=Workspace name NewGroupPanel.nameField.AccessibleContext.accessibleName=Name -NewGroupPanel.nameField.AccessibleContext.accessibleDescription=Group name -NewGroupPanel.adHocKindRadio.AccessibleContext.accessibleDescription=Free group +NewGroupPanel.nameField.AccessibleContext.accessibleDescription=Workspace name +NewGroupPanel.adHocKindRadio.AccessibleContext.accessibleDescription=Free workspace NewGroupPanel.adHocKindLabel.AccessibleContext.accessibleDescription=Contains any projects you like. Can be updated manually or automatically. NewGroupPanel.useOpenCheckbox.AccessibleContext.accessibleDescription=Use Currently Open Projects NewGroupPanel.autoSynchCheckbox.AccessibleContext.accessibleDescription=Automatically Save Project List @@ -80,11 +80,11 @@ NewGroupPanel.directoryLabel.AccessibleContext.accessibleDescription=Folder NewGroupPanel.directoryField.AccessibleContext.accessibleName=Folder NewGroupPanel.directoryField.AccessibleContext.accessibleDescription=Folder of projects NewGroupPanel.directoryButton.AccessibleContext.accessibleDescription=Browse -RemoveGroupPanel.selectionLabel.text=Select Group(s) to remove -RemoveGroupPanel.selectionLabel.text=Select Group(s) to remove +RemoveGroupPanel.selectionLabel.text=Select Workspace(s) to remove +RemoveGroupPanel.selectionLabel.text=Select Workspace(s) to remove ManageGroupsPanel.propertiesButton.text=&Properties ManageGroupsPanel.removeButton.text=&Remove -ManageGroupsPanel.selectionLabel.text=Select Group(s) +ManageGroupsPanel.selectionLabel.text=Select Workspace(s) ManageGroupsPanel.removeAllButton.text=Remove &All DirectoryGroupEditPanel.masterProjectButton.text=&Browse... DirectoryGroupEditPanel.directoryButton.text=Bro&wse... diff --git a/ide/projectui/src/org/netbeans/modules/project/ui/groups/Group.java b/ide/projectui/src/org/netbeans/modules/project/ui/groups/Group.java index 42d7b6d10d7b..144acfd3b2ce 100644 --- a/ide/projectui/src/org/netbeans/modules/project/ui/groups/Group.java +++ b/ide/projectui/src/org/netbeans/modules/project/ui/groups/Group.java @@ -74,7 +74,7 @@ import org.openide.windows.WindowSystemListener; /** - * Represents a project group. + * Represents a project group aka workspace. * Static methods represent set of groups and group selection. * @author Jesse Glick */ @@ -149,7 +149,7 @@ public static Group getActiveGroup() { * Set the currently active group (or null). */ @Messages({ - "# {0} - internal group info", "Group.UI.setActiveGroup=Selecting project group: {0}", + "# {0} - internal group info", "Group.UI.setActiveGroup=Selecting workspace: {0}", "#NOI18N", "Group.UI.setActiveGroup_ICON_BASE=org/netbeans/modules/project/ui/resources/openProject.png" }) public static void setActiveGroup(Group nue, boolean isNewGroup) { diff --git a/ide/projectui/src/org/netbeans/modules/project/ui/groups/GroupOptionProcessor.java b/ide/projectui/src/org/netbeans/modules/project/ui/groups/GroupOptionProcessor.java index 5ee2dfe97250..f7fbe2e4ad8b 100644 --- a/ide/projectui/src/org/netbeans/modules/project/ui/groups/GroupOptionProcessor.java +++ b/ide/projectui/src/org/netbeans/modules/project/ui/groups/GroupOptionProcessor.java @@ -36,21 +36,21 @@ public class GroupOptionProcessor implements ArgsProcessor { ) @Messages({ "GroupOptionProcessor.open.name=--open-group NAME", - "GroupOptionProcessor.open.desc=open a project group by shortened or full name (or unique substring)" + "GroupOptionProcessor.open.desc=open a workspace by shortened or full name (or unique substring)" }) public String openOption; @Arg(longName="close-group") @Description( shortDescription="#GroupOptionProcessor.close.desc" ) - @Messages("GroupOptionProcessor.close.desc=close any open project group") + @Messages("GroupOptionProcessor.close.desc=close any open workspace") public boolean closeOption; @Arg(longName="list-groups") @Description( shortDescription="#GroupOptionProcessor.list.desc" ) - @Messages("GroupOptionProcessor.list.desc=list available project groups") + @Messages("GroupOptionProcessor.list.desc=list available workspaces") public boolean listOption; @Messages({ diff --git a/ide/projectui/src/org/netbeans/modules/project/ui/groups/GroupsMenu.java b/ide/projectui/src/org/netbeans/modules/project/ui/groups/GroupsMenu.java index 4948298132d4..7b4ab517eed4 100644 --- a/ide/projectui/src/org/netbeans/modules/project/ui/groups/GroupsMenu.java +++ b/ide/projectui/src/org/netbeans/modules/project/ui/groups/GroupsMenu.java @@ -53,7 +53,7 @@ @ActionReference(path = "Menu/File", position = 1100), @ActionReference(path = ProjectsRootNode.ACTIONS_FOLDER, position = 600, separatorAfter = 700) }) -@Messages("GroupsMenu.label=Project Gro&ups...") +@Messages("GroupsMenu.label=Workspace &Manager...") public class GroupsMenu extends AbstractAction { private static final RequestProcessor RP = new RequestProcessor(GroupsMenu.class.getName()); @@ -72,8 +72,8 @@ public void actionPerformed(ActionEvent e) { * Create (and open) a new group. */ @Messages({ - "GroupsMenu.new_title=Create New Group", - "GroupsMenu.new_create=Create Group", + "GroupsMenu.new_title=Create New Workspace", + "GroupsMenu.new_create=Create Workspace", "GroupsMenu.new_close=Close" }) private static void newGroup() { @@ -116,9 +116,9 @@ private static void newGroup() { * Manage groups. */ @Messages({ - "GroupsMenu.manage_title=Manage Groups", - "GroupsMenu.manage_select_group=&Select Group", - "GroupsMenu.manage_new_group=&New Group...", + "GroupsMenu.manage_title=Manage Workspaces", + "GroupsMenu.manage_select_group=&Select Workspace", + "GroupsMenu.manage_new_group=&New Workspace...", "GroupsMenu.manage_remove=&Remove", "GroupsMenu.manage_close=Close", "GroupsMenu.manage_properties=&Properties", @@ -155,7 +155,7 @@ private static void manageGroups() { /** * Open a properties dialog for the group, according to its type. */ - @Messages("GroupsMenu.properties_title=Project Group Properties") + @Messages("GroupsMenu.properties_title=Workspace Properties") static void openProperties(Group g) { Lookup context = Lookups.fixed(new Object[] { g, BaseUtilities.ACCESSOR.createGroup(g.getName(), g.prefs()) }); Dialog dialog = ProjectCustomizer.createCustomizerDialog("Projects/Groups/Customizer", //NOI18N diff --git a/ide/projectui/src/org/netbeans/modules/project/ui/groups/NewGroupPanel.java b/ide/projectui/src/org/netbeans/modules/project/ui/groups/NewGroupPanel.java index 53fff6df79a4..1f803ba94979 100644 --- a/ide/projectui/src/org/netbeans/modules/project/ui/groups/NewGroupPanel.java +++ b/ide/projectui/src/org/netbeans/modules/project/ui/groups/NewGroupPanel.java @@ -43,7 +43,7 @@ import org.openide.util.NbBundle.Messages; /** - * Panel permitting user to create a new project group. + * Panel permitting user to create a new project group aka workspace. * Applicable in advanced mode. * @author Jesse Glick */ From 884b1765c892fdd83da5d6b75a3edacbc2a4255b Mon Sep 17 00:00:00 2001 From: Jaroslav Tulach Date: Wed, 23 Sep 2026 18:09:20 +0200 Subject: [PATCH 3/4] findProjectOrFallback to make sure each DirectoryGroup contains its root --- .../modules/projectapi/nb/GenericPrj.java | 45 +++++++++++++++++++ .../projectapi/nb/NbProjectManager.java | 36 +++++++++++---- .../netbeans/api/project/ProjectManager.java | 16 +++++++ .../project/ProjectManagerImplementation.java | 6 +++ .../api/project/ProjectManagerTest.java | 23 ++++++++++ .../project/ui/groups/DirectoryGroup.java | 2 +- 6 files changed, 119 insertions(+), 9 deletions(-) create mode 100644 ide/projectapi.nb/src/org/netbeans/modules/projectapi/nb/GenericPrj.java diff --git a/ide/projectapi.nb/src/org/netbeans/modules/projectapi/nb/GenericPrj.java b/ide/projectapi.nb/src/org/netbeans/modules/projectapi/nb/GenericPrj.java new file mode 100644 index 000000000000..f88a814ad5fc --- /dev/null +++ b/ide/projectapi.nb/src/org/netbeans/modules/projectapi/nb/GenericPrj.java @@ -0,0 +1,45 @@ +/* + * 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.projectapi.nb; + +import org.netbeans.api.project.Project; +import org.openide.filesystems.FileObject; +import org.openide.util.Lookup; +import org.openide.util.lookup.Lookups; + +final class GenericPrj implements Project { + private final FileObject dir; + private final Lookup lkp; + + public GenericPrj(FileObject dir) { + this.dir = dir; + this.lkp = Lookups.fixed(this); + } + + @Override + public FileObject getProjectDirectory() { + return dir; + } + + @Override + public Lookup getLookup() { + return lkp; + } + +} diff --git a/ide/projectapi.nb/src/org/netbeans/modules/projectapi/nb/NbProjectManager.java b/ide/projectapi.nb/src/org/netbeans/modules/projectapi/nb/NbProjectManager.java index 372cd5f82ee5..f27e1dcdc6d1 100644 --- a/ide/projectapi.nb/src/org/netbeans/modules/projectapi/nb/NbProjectManager.java +++ b/ide/projectapi.nb/src/org/netbeans/modules/projectapi/nb/NbProjectManager.java @@ -65,7 +65,7 @@ * @author Jesse Glick */ @ServiceProvider(service = ProjectManagerImplementation.class, position = 1000) -public final class NbProjectManager implements ProjectManagerImplementation { +public final class NbProjectManager implements ProjectManagerImplementation.WithFallback { // XXX need to figure out how to convince the system that a Project object is modified // so that Save All and the exit dialog work... could temporarily use a DataLoader @@ -90,7 +90,7 @@ public void resultChanged(LookupEvent e) { } }); } - + private static enum LoadStatus { /** * Marker for a directory which is known to not be a project. @@ -212,6 +212,17 @@ public Mutex getMutex( */ @Override public Project findProject(final FileObject projectDirectory) throws IOException, IllegalArgumentException { + return findProjectImpl(projectDirectory, false); + } + + @Override + public Project findProjectOrFallback(FileObject projectDirectory) throws IOException, IllegalArgumentException { + var found = findProjectImpl(projectDirectory, true); + assert found != null; + return found; + } + + private Project findProjectImpl(FileObject projectDirectory, boolean fallback) throws IOException, IllegalArgumentException { Parameters.notNull("projectDirectory", projectDirectory); //NOI18N try { return getMutex().readAccess(new Mutex.ExceptionAction() { @@ -250,11 +261,17 @@ public Project run() throws IOException { assert !LoadStatus.LOADING_PROJECT.is(o); wasSomeSuchProject = LoadStatus.SOME_SUCH_PROJECT.is(o); if (LoadStatus.NO_SUCH_PROJECT.is(o)) { - if (LOG.isLoggable(Level.FINE)) { - LOG.log(Level.FINE, "findProject({0}) in {1}: NO_SUCH_PROJECT", new Object[] {projectDirectory, Thread.currentThread().getName()}); + if (fallback) { + // treat a not checked project yet + o = null; + } else { + if (LOG.isLoggable(Level.FINE)) { + LOG.log(Level.FINE, "findProject({0}) in {1}: NO_SUCH_PROJECT", new Object[]{projectDirectory, Thread.currentThread().getName()}); + } + return null; } - return null; - } else if (o != null && !LoadStatus.SOME_SUCH_PROJECT.is(o)) { + } + if (o != null && !LoadStatus.SOME_SUCH_PROJECT.is(o)) { Project p = o.first().get(); if (p != null) { if (LOG.isLoggable(Level.FINE)) { @@ -285,7 +302,7 @@ public Project run() throws IOException { } boolean resetLP = false; try { - Project p = createProject(projectDirectory); + Project p = createProject(projectDirectory, fallback); //Thread.dumpStack(); synchronized (dir2Proj) { dir2Proj.notifyAll(); @@ -366,13 +383,16 @@ public Project run() throws IOException { * @return a project made from it, or null if it is not recognized * @throws IOException if there was a problem loading the project */ - private Project createProject(FileObject dir) throws IOException { + private Project createProject(FileObject dir, boolean fallback) throws IOException { assert dir != null; assert dir.isFolder(); assert getMutex().isReadAccess(); ProjectStateImpl state = new ProjectStateImpl(); for (ProjectFactory factory : factories.allInstances()) { Project p = factory.loadProject(dir, state); + if (p == null && fallback) { + p = new GenericPrj(dir); + } if (p != null) { if (TIMERS.isLoggable(Level.FINE)) { LogRecord rec = new LogRecord(Level.FINE, "Project"); // NOI18N diff --git a/ide/projectapi/src/org/netbeans/api/project/ProjectManager.java b/ide/projectapi/src/org/netbeans/api/project/ProjectManager.java index 81da1b4c7178..9b8a77e2a34b 100644 --- a/ide/projectapi/src/org/netbeans/api/project/ProjectManager.java +++ b/ide/projectapi/src/org/netbeans/api/project/ProjectManager.java @@ -141,6 +141,22 @@ public Project findProject(@NonNull final FileObject projectDirectory) throws IO } return impl.findProject(projectDirectory); } + + /** @since 1.111 */ + @NonNull + public Project findProjectOrFallback(@NonNull FileObject projectDirectory) throws IOException, IllegalArgumentException { + if (projectDirectory == null) { + throw new IllegalArgumentException("Attempted to pass a null directory to findProject"); // NOI18N + } + if (!projectDirectory.isFolder()) { + throw new IllegalArgumentException("Attempted to pass a non-directory to findProject: " + projectDirectory); // NOI18N + } + if (impl instanceof ProjectManagerImplementation.WithFallback implV2) { + return implV2.findProjectOrFallback(projectDirectory); + } else { + throw new IllegalArgumentException("Cannot create fallback project for " + projectDirectory); // NOI18N + } + } /** diff --git a/ide/projectapi/src/org/netbeans/spi/project/ProjectManagerImplementation.java b/ide/projectapi/src/org/netbeans/spi/project/ProjectManagerImplementation.java index c95437bd9204..ab33b296f18b 100644 --- a/ide/projectapi/src/org/netbeans/spi/project/ProjectManagerImplementation.java +++ b/ide/projectapi/src/org/netbeans/spi/project/ProjectManagerImplementation.java @@ -182,6 +182,12 @@ Mutex getMutex( */ void saveAllProjects() throws IOException; + /** @since 1.111 */ + interface WithFallback extends ProjectManagerImplementation { + @NonNull + Project findProjectOrFallback(@NonNull FileObject projectDirectory) throws IOException, IllegalArgumentException; + } + /** * Callback to notify the {@link ProjectManager} about changes. */ diff --git a/ide/projectapi/test/unit/src/org/netbeans/api/project/ProjectManagerTest.java b/ide/projectapi/test/unit/src/org/netbeans/api/project/ProjectManagerTest.java index eec0751279b4..dbfe16cc51a8 100644 --- a/ide/projectapi/test/unit/src/org/netbeans/api/project/ProjectManagerTest.java +++ b/ide/projectapi/test/unit/src/org/netbeans/api/project/ProjectManagerTest.java @@ -65,6 +65,7 @@ public ProjectManagerTest(String name) { private FileObject goodproject2; private FileObject badproject; private FileObject mysteryproject; + private FileObject justADir; private ProjectManager pm; protected @Override Level logLevel() { @@ -82,6 +83,7 @@ protected void setUp() throws Exception { badproject = scratch.createFolder("bad"); badproject.createFolder("testproject").createData("broken"); mysteryproject = scratch.createFolder("mystery"); + justADir = scratch.createFolder("justADir"); MockLookup.setInstances(TestUtil.testProjectFactory()); pm = ProjectManager.getDefault(); NbProjectManagerAccessor.reset(); @@ -206,6 +208,27 @@ public void testIsProject() throws Exception { assertFalse("Should not have been able to load mysteryproject", pm.isProject(mysteryproject)); } + public void testIsFallbackProject() throws Exception { + var nothing = pm.findProject(justADir); + assertNull("No project is found for just a dir", nothing); + var generic = pm.findProjectOrFallback(justADir); + assertNotNull("But one can ask for a fallback project", generic); + + var then = pm.findProject(justADir); + assertSame("since then findProject works for just a dir", generic, then); + + var ref = new WeakReference<>(generic); + generic = null; + then = null; + // give the references time to disappear + Thread.sleep(TimedWeakReference.TIMEOUT); + + assertGC("The fallback project gets GCed when no longer used", ref); + + var nothingAgain = pm.findProject(justADir); + assertNull("Since then, findProject again returns null", nothingAgain); + } + public void testIsProject2() throws Exception { ProjectManager.Result r = pm.isProject2(goodproject); assertNotNull("Should have recognized goodproject", r); diff --git a/ide/projectui/src/org/netbeans/modules/project/ui/groups/DirectoryGroup.java b/ide/projectui/src/org/netbeans/modules/project/ui/groups/DirectoryGroup.java index adacb9bb6248..c70d46568f35 100644 --- a/ide/projectui/src/org/netbeans/modules/project/ui/groups/DirectoryGroup.java +++ b/ide/projectui/src/org/netbeans/modules/project/ui/groups/DirectoryGroup.java @@ -77,7 +77,7 @@ protected void findProjects(Set projects, ProgressHandle h, int start, } if (fo != null && fo.isFolder()) { try { - Project p = ProjectManager.getDefault().findProject(fo); + Project p = ProjectManager.getDefault().findProjectOrFallback(fo); if (p != null) { projects.add(p); if (h != null) { From 6bcb9b3da79cf4f99e840e640d34686344ab52af Mon Sep 17 00:00:00 2001 From: Jaroslav Tulach Date: Fri, 25 Sep 2026 11:37:13 +0200 Subject: [PATCH 4/4] Avoid changing the naming --- ide/projectui/apichanges.xml | 2 +- ide/projectui/arch.xml | 4 +- .../modules/project/ui/Bundle.properties | 2 +- .../project/ui/OpenProjectListSettings.java | 2 +- .../project/ui/groups/Bundle.properties | 44 +++++++++---------- .../modules/project/ui/groups/Group.java | 4 +- .../ui/groups/GroupOptionProcessor.java | 6 +-- .../modules/project/ui/groups/GroupsMenu.java | 14 +++--- .../project/ui/groups/NewGroupPanel.java | 2 +- 9 files changed, 40 insertions(+), 40 deletions(-) diff --git a/ide/projectui/apichanges.xml b/ide/projectui/apichanges.xml index aec1c9e24fd6..fdacf3f9e48c 100644 --- a/ide/projectui/apichanges.xml +++ b/ide/projectui/apichanges.xml @@ -70,7 +70,7 @@

    @ProjectCustomizer.CompositeCategoryProvider.Registration annotation now accepts "Groups" projectType parameter value which - adds a panel to the workspace customizer. Implementations can expect an instance of org.netbeans.api.project.ui.ProjectGroup in the lookup + adds a panel to the Project Group customizer. Implementations can expect an instance of org.netbeans.api.project.ui.ProjectGroup in the lookup passed to the provider. NO central storing procedure in place, each panel is expected to set it's own values in Category.setStoreListener

    diff --git a/ide/projectui/arch.xml b/ide/projectui/arch.xml index 46a9a8c7c67b..9215ba685eee 100644 --- a/ide/projectui/arch.xml +++ b/ide/projectui/arch.xml @@ -623,7 +623,7 @@
  • - Can use --open-group NAME to open a workspace at startup, or --close-group. + Can use --open-group NAME to open a project group at startup, or --close-group.

  • @@ -1155,7 +1155,7 @@

    - Workspaces. See Group. + Project groups. See Group.

    diff --git a/ide/projectui/src/org/netbeans/modules/project/ui/Bundle.properties b/ide/projectui/src/org/netbeans/modules/project/ui/Bundle.properties index 84822327978e..a2b923f28664 100644 --- a/ide/projectui/src/org/netbeans/modules/project/ui/Bundle.properties +++ b/ide/projectui/src/org/netbeans/modules/project/ui/Bundle.properties @@ -50,7 +50,7 @@ LBL_ProjectMode=Projects #ExportOptions Export_Category_Display_Name=Projects Export_Display_Name=Basic -Export_Groups_Display_Name=Workspaces +Export_Groups_Display_Name=Project Groups #Nodes LBL_OpenProjectsNode_Name=Projects diff --git a/ide/projectui/src/org/netbeans/modules/project/ui/OpenProjectListSettings.java b/ide/projectui/src/org/netbeans/modules/project/ui/OpenProjectListSettings.java index 87a0189679ee..fa602d15f00f 100644 --- a/ide/projectui/src/org/netbeans/modules/project/ui/OpenProjectListSettings.java +++ b/ide/projectui/src/org/netbeans/modules/project/ui/OpenProjectListSettings.java @@ -231,7 +231,7 @@ public void setOpenProjectsIcons( List list ) { } public boolean isOpenSubprojects() { - return getPreferences().getBoolean( OPEN_SUBPROJECTS, true); + return getPreferences().getBoolean( OPEN_SUBPROJECTS, false); } public void setOpenSubprojects( boolean openSubprojects ) { diff --git a/ide/projectui/src/org/netbeans/modules/project/ui/groups/Bundle.properties b/ide/projectui/src/org/netbeans/modules/project/ui/groups/Bundle.properties index 9b54d855d7cd..9d426f8d6511 100644 --- a/ide/projectui/src/org/netbeans/modules/project/ui/groups/Bundle.properties +++ b/ide/projectui/src/org/netbeans/modules/project/ui/groups/Bundle.properties @@ -36,35 +36,35 @@ SubprojectsGroupEditPanel.masterProjectLabel.text=&Master Project: DirectoryGroupEditPanel.nameLabel.text=&Name\: DirectoryGroupEditPanel.folderLabel.text=&Folder\: -SubprojectsGroupEditPanel.nameLabel.AccessibleContext.accessibleDescription=Name of the workspace +SubprojectsGroupEditPanel.nameLabel.AccessibleContext.accessibleDescription=Name of the project group SubprojectsGroupEditPanel.masterProjectLabel.AccessibleContext.accessibleDescription=Group master project name SubprojectsGroupEditPanel.nameField.AccessibleContext.accessibleName=Name -SubprojectsGroupEditPanel.nameField.AccessibleContext.accessibleDescription=Workspace name +SubprojectsGroupEditPanel.nameField.AccessibleContext.accessibleDescription=Project group name SubprojectsGroupEditPanel.masterProjectField.AccessibleContext.accessibleName= SubprojectsGroupEditPanel.masterProjectField.AccessibleContext.accessibleDescription= -SubprojectsGroupEditPanel.AccessibleContext.accessibleName=Workspace properties -SubprojectsGroupEditPanel.AccessibleContext.accessibleDescription=Workspace properties -DirectoryGroupEditPanel.nameLabel.AccessibleContext.accessibleDescription=Name of the workspace -DirectoryGroupEditPanel.folderLabel.AccessibleContext.accessibleDescription=Workspace folder +SubprojectsGroupEditPanel.AccessibleContext.accessibleName=Project group properties +SubprojectsGroupEditPanel.AccessibleContext.accessibleDescription=Project group properties +DirectoryGroupEditPanel.nameLabel.AccessibleContext.accessibleDescription=Name of the project group +DirectoryGroupEditPanel.folderLabel.AccessibleContext.accessibleDescription=Project group folder DirectoryGroupEditPanel.nameField.AccessibleContext.accessibleName=Name -DirectoryGroupEditPanel.nameField.AccessibleContext.accessibleDescription=Workspace name +DirectoryGroupEditPanel.nameField.AccessibleContext.accessibleDescription=Project group name DirectoryGroupEditPanel.folderField.AccessibleContext.accessibleName= DirectoryGroupEditPanel.folderField.AccessibleContext.accessibleDescription= -DirectoryGroupEditPanel.AccessibleContext.accessibleName=Workspace properties -DirectoryGroupEditPanel.AccessibleContext.accessibleDescription=Workspace properties -AdHocGroupEditPanel.AccessibleContext.accessibleName=Workspace properties -AdHocGroupEditPanel.AccessibleContext.accessibleDescription=Workspace properties -AdHocGroupEditPanel.nameLabel.AccessibleContext.accessibleDescription=Workspace name +DirectoryGroupEditPanel.AccessibleContext.accessibleName=Project group properties +DirectoryGroupEditPanel.AccessibleContext.accessibleDescription=Project group properties +AdHocGroupEditPanel.AccessibleContext.accessibleName=Project group properties +AdHocGroupEditPanel.AccessibleContext.accessibleDescription=Project group properties +AdHocGroupEditPanel.nameLabel.AccessibleContext.accessibleDescription=Project group name AdHocGroupEditPanel.nameField.AccessibleContext.accessibleName=Name -AdHocGroupEditPanel.nameField.AccessibleContext.accessibleDescription=Workspace name -AdHocGroupEditPanel.autoSynchCheckbox.AccessibleContext.accessibleDescription=Automatically save workspace list +AdHocGroupEditPanel.nameField.AccessibleContext.accessibleDescription=Project group name +AdHocGroupEditPanel.autoSynchCheckbox.AccessibleContext.accessibleDescription=Automatically save project group list AdHocGroupEditPanel.synchButton.AccessibleContext.accessibleDescription=Save current project list now -NewGroupPanel.AccessibleContext.accessibleName=New workspace -NewGroupPanel.AccessibleContext.accessibleDescription=New workspace -NewGroupPanel.nameLabel.AccessibleContext.accessibleDescription=Workspace name +NewGroupPanel.AccessibleContext.accessibleName=New project group +NewGroupPanel.AccessibleContext.accessibleDescription=New project group +NewGroupPanel.nameLabel.AccessibleContext.accessibleDescription=Group name NewGroupPanel.nameField.AccessibleContext.accessibleName=Name -NewGroupPanel.nameField.AccessibleContext.accessibleDescription=Workspace name -NewGroupPanel.adHocKindRadio.AccessibleContext.accessibleDescription=Free workspace +NewGroupPanel.nameField.AccessibleContext.accessibleDescription=Group name +NewGroupPanel.adHocKindRadio.AccessibleContext.accessibleDescription=Free group NewGroupPanel.adHocKindLabel.AccessibleContext.accessibleDescription=Contains any projects you like. Can be updated manually or automatically. NewGroupPanel.useOpenCheckbox.AccessibleContext.accessibleDescription=Use Currently Open Projects NewGroupPanel.autoSynchCheckbox.AccessibleContext.accessibleDescription=Automatically Save Project List @@ -80,11 +80,11 @@ NewGroupPanel.directoryLabel.AccessibleContext.accessibleDescription=Folder NewGroupPanel.directoryField.AccessibleContext.accessibleName=Folder NewGroupPanel.directoryField.AccessibleContext.accessibleDescription=Folder of projects NewGroupPanel.directoryButton.AccessibleContext.accessibleDescription=Browse -RemoveGroupPanel.selectionLabel.text=Select Workspace(s) to remove -RemoveGroupPanel.selectionLabel.text=Select Workspace(s) to remove +RemoveGroupPanel.selectionLabel.text=Select Group(s) to remove +RemoveGroupPanel.selectionLabel.text=Select Group(s) to remove ManageGroupsPanel.propertiesButton.text=&Properties ManageGroupsPanel.removeButton.text=&Remove -ManageGroupsPanel.selectionLabel.text=Select Workspace(s) +ManageGroupsPanel.selectionLabel.text=Select Group(s) ManageGroupsPanel.removeAllButton.text=Remove &All DirectoryGroupEditPanel.masterProjectButton.text=&Browse... DirectoryGroupEditPanel.directoryButton.text=Bro&wse... diff --git a/ide/projectui/src/org/netbeans/modules/project/ui/groups/Group.java b/ide/projectui/src/org/netbeans/modules/project/ui/groups/Group.java index 144acfd3b2ce..42d7b6d10d7b 100644 --- a/ide/projectui/src/org/netbeans/modules/project/ui/groups/Group.java +++ b/ide/projectui/src/org/netbeans/modules/project/ui/groups/Group.java @@ -74,7 +74,7 @@ import org.openide.windows.WindowSystemListener; /** - * Represents a project group aka workspace. + * Represents a project group. * Static methods represent set of groups and group selection. * @author Jesse Glick */ @@ -149,7 +149,7 @@ public static Group getActiveGroup() { * Set the currently active group (or null). */ @Messages({ - "# {0} - internal group info", "Group.UI.setActiveGroup=Selecting workspace: {0}", + "# {0} - internal group info", "Group.UI.setActiveGroup=Selecting project group: {0}", "#NOI18N", "Group.UI.setActiveGroup_ICON_BASE=org/netbeans/modules/project/ui/resources/openProject.png" }) public static void setActiveGroup(Group nue, boolean isNewGroup) { diff --git a/ide/projectui/src/org/netbeans/modules/project/ui/groups/GroupOptionProcessor.java b/ide/projectui/src/org/netbeans/modules/project/ui/groups/GroupOptionProcessor.java index f7fbe2e4ad8b..5ee2dfe97250 100644 --- a/ide/projectui/src/org/netbeans/modules/project/ui/groups/GroupOptionProcessor.java +++ b/ide/projectui/src/org/netbeans/modules/project/ui/groups/GroupOptionProcessor.java @@ -36,21 +36,21 @@ public class GroupOptionProcessor implements ArgsProcessor { ) @Messages({ "GroupOptionProcessor.open.name=--open-group NAME", - "GroupOptionProcessor.open.desc=open a workspace by shortened or full name (or unique substring)" + "GroupOptionProcessor.open.desc=open a project group by shortened or full name (or unique substring)" }) public String openOption; @Arg(longName="close-group") @Description( shortDescription="#GroupOptionProcessor.close.desc" ) - @Messages("GroupOptionProcessor.close.desc=close any open workspace") + @Messages("GroupOptionProcessor.close.desc=close any open project group") public boolean closeOption; @Arg(longName="list-groups") @Description( shortDescription="#GroupOptionProcessor.list.desc" ) - @Messages("GroupOptionProcessor.list.desc=list available workspaces") + @Messages("GroupOptionProcessor.list.desc=list available project groups") public boolean listOption; @Messages({ diff --git a/ide/projectui/src/org/netbeans/modules/project/ui/groups/GroupsMenu.java b/ide/projectui/src/org/netbeans/modules/project/ui/groups/GroupsMenu.java index 7b4ab517eed4..4948298132d4 100644 --- a/ide/projectui/src/org/netbeans/modules/project/ui/groups/GroupsMenu.java +++ b/ide/projectui/src/org/netbeans/modules/project/ui/groups/GroupsMenu.java @@ -53,7 +53,7 @@ @ActionReference(path = "Menu/File", position = 1100), @ActionReference(path = ProjectsRootNode.ACTIONS_FOLDER, position = 600, separatorAfter = 700) }) -@Messages("GroupsMenu.label=Workspace &Manager...") +@Messages("GroupsMenu.label=Project Gro&ups...") public class GroupsMenu extends AbstractAction { private static final RequestProcessor RP = new RequestProcessor(GroupsMenu.class.getName()); @@ -72,8 +72,8 @@ public void actionPerformed(ActionEvent e) { * Create (and open) a new group. */ @Messages({ - "GroupsMenu.new_title=Create New Workspace", - "GroupsMenu.new_create=Create Workspace", + "GroupsMenu.new_title=Create New Group", + "GroupsMenu.new_create=Create Group", "GroupsMenu.new_close=Close" }) private static void newGroup() { @@ -116,9 +116,9 @@ private static void newGroup() { * Manage groups. */ @Messages({ - "GroupsMenu.manage_title=Manage Workspaces", - "GroupsMenu.manage_select_group=&Select Workspace", - "GroupsMenu.manage_new_group=&New Workspace...", + "GroupsMenu.manage_title=Manage Groups", + "GroupsMenu.manage_select_group=&Select Group", + "GroupsMenu.manage_new_group=&New Group...", "GroupsMenu.manage_remove=&Remove", "GroupsMenu.manage_close=Close", "GroupsMenu.manage_properties=&Properties", @@ -155,7 +155,7 @@ private static void manageGroups() { /** * Open a properties dialog for the group, according to its type. */ - @Messages("GroupsMenu.properties_title=Workspace Properties") + @Messages("GroupsMenu.properties_title=Project Group Properties") static void openProperties(Group g) { Lookup context = Lookups.fixed(new Object[] { g, BaseUtilities.ACCESSOR.createGroup(g.getName(), g.prefs()) }); Dialog dialog = ProjectCustomizer.createCustomizerDialog("Projects/Groups/Customizer", //NOI18N diff --git a/ide/projectui/src/org/netbeans/modules/project/ui/groups/NewGroupPanel.java b/ide/projectui/src/org/netbeans/modules/project/ui/groups/NewGroupPanel.java index 1f803ba94979..53fff6df79a4 100644 --- a/ide/projectui/src/org/netbeans/modules/project/ui/groups/NewGroupPanel.java +++ b/ide/projectui/src/org/netbeans/modules/project/ui/groups/NewGroupPanel.java @@ -43,7 +43,7 @@ import org.openide.util.NbBundle.Messages; /** - * Panel permitting user to create a new project group aka workspace. + * Panel permitting user to create a new project group. * Applicable in advanced mode. * @author Jesse Glick */