Skip to content

Commit 9f212e8

Browse files
committed
Have "Close All" and friends honor "Cancel" in save prompt dialogs
When invoking "Close All" on a tab, some tabs may pop up confirmation dialogs asking whether their work should be saved or not. Pressing the "Cancel" button in any dialog, as opposed to "Save" or "Discard", should stop showing further dialogs, and stop the overall operation. This was not previously done. (In some cases, a bunch of dialogs would be shown on top of each other simultaneously, creating visual artifacts.) This change was made to the Close All and Close Other actions, as well as for the newly introduced "Close whole documents list at once" button in the document list dropdown (PR #4792). In a previous discussion, it was suggested to make the whole operation atomic (i.e. don't close any tabs at all if the user presses Cancel after several tabs have already been iterated), but this is not possible given the existing APIs. (We don't know if Cancel was pressed until TopComponent.close() was called.) Moreover, the behavior implemented in this PR is more standard in any case; it matches the behavior in e.g. Photoshop and VSCode. It was also suggested to implement similar behavior in the save prompt that shows up when exiting the IDE, but this is a rather different operation that does not actually close any tabs (they remain "open" and persisted for the next time the IDE starts). I'm not quite sure how to improve it; it might require more discussion. See the "Savable" API and the o.n.core.ExitDialog class. I would also have liked to focus each TopComponent in turn when prompts to save come up, but this is a bit more work due to the various levels of abstractions involved. More work on this can be done later. See MultiViewFactory.resolveCloseOperation for a relevant starting point.
1 parent cf48b1f commit 9f212e8

File tree

2 files changed

+50
-48
lines changed

2 files changed

+50
-48
lines changed

platform/core.multitabs/src/org/netbeans/core/multitabs/impl/DocumentSwitcherTable.java

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@
4949
import org.netbeans.swing.tabcontrol.TabbedContainer;
5050
import org.netbeans.swing.tabcontrol.event.TabActionEvent;
5151
import org.openide.awt.CloseButtonFactory;
52+
import org.openide.windows.TopComponent;
5253

5354
/**
5455
* Slightly enhanced switcher table which adds close button to selected item
@@ -196,15 +197,35 @@ boolean closeSelectedDocumentList() {
196197
for ( TabData tab : tabs ) {
197198
ProjectProxy projectForTab = projectSupport.getProjectForTab( tab );
198199
if (( project == null && projectForTab == null ) || ( projectForTab != null && projectForTab.equals( project ))) {
199-
int tabIndex = controller.getTabModel().indexOf( tab );
200-
TabActionEvent tae = new TabActionEvent( this, TabbedContainer.COMMAND_CLOSE, tabIndex );
201-
controller.postActionEvent( tae );
200+
Component tabComponent = tab.getComponent();
201+
if (tabComponent instanceof TopComponent) {
202+
TopComponent curTC = (TopComponent) tabComponent;
203+
/* As in o.n.core.windows.actions.ActionUtils.closeAll(Iterable<TopComponent>).
204+
We have to be a little more general here since, theoretically, there could be
205+
tab components that do not extend from TopComponent. */
206+
if(!isClosingEnabled(curTC)) {
207+
continue;
208+
}
209+
curTC.putClientProperty("inCloseAll", Boolean.TRUE);
210+
if (!curTC.close()) {
211+
break;
212+
}
213+
} else {
214+
int tabIndex = controller.getTabModel().indexOf( tab );
215+
TabActionEvent tae = new TabActionEvent( this, TabbedContainer.COMMAND_CLOSE, tabIndex );
216+
controller.postActionEvent( tae );
217+
}
202218
} else {
203219
numOfOtherTabs++;
204220
}
205221
}
206222
return numOfOtherTabs == 0;
207223
}
224+
225+
// Copied from o.n.core.windows.Switches.isClosingEnabled (in private package).
226+
private static boolean isClosingEnabled( TopComponent tc ) {
227+
return !Boolean.TRUE.equals(tc.getClientProperty(TopComponent.PROP_CLOSING_DISABLED));
228+
}
208229

209230
private JButton createCloseButton() {
210231
JButton res = CloseButtonFactory.createBigCloseButton();

platform/core.windows/src/org/netbeans/core/windows/actions/ActionUtils.java

Lines changed: 26 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,6 @@
2929
import org.netbeans.core.windows.view.ui.slides.SlideController;
3030
import org.openide.DialogDisplayer;
3131
import org.openide.NotifyDescriptor;
32-
import org.openide.awt.Actions;
3332
import org.openide.awt.Mnemonics;
3433
import org.openide.cookies.SaveCookie;
3534
import org.openide.util.*;
@@ -467,30 +466,15 @@ public void actionPerformed(ActionEvent evt) {
467466
* otherwise closes all documents in the system
468467
*/
469468
public static void closeAllDocuments (boolean isContext) {
470-
if (isContext) {
471-
TopComponent activeTC = TopComponent.getRegistry().getActivated();
472-
List<TopComponent> tcs = getOpened(activeTC);
473-
474-
closeAll( tcs.toArray(new TopComponent[tcs.size()]) );
475-
} else {
476-
TopComponent[] tcs = WindowManagerImpl.getInstance().getEditorTopComponents();
477-
closeAll( tcs );
478-
}
479-
}
480-
481-
private static void closeAll( TopComponent[] tcs ) {
482-
for( TopComponent tc: tcs ) {
483-
if( !Switches.isClosingEnabled(tc) )
484-
continue;
485-
final TopComponent toBeClosed = tc;
486-
SwingUtilities.invokeLater( new Runnable() {
487-
@Override
488-
public void run() {
489-
toBeClosed.putClientProperty("inCloseAll", Boolean.TRUE);
490-
toBeClosed.close();
491-
}
492-
});
493-
}
469+
/* Historically, the closeAll method wrapped calls to TopComponent.close() in an
470+
invokeLater, so keep doing that. This is probably a good idea e.g. for cases where the
471+
caller is a button press handler and where TopComponent.close() ends up showing a modal
472+
dialog (which could perhaps delay the update of the button's visual state). */
473+
SwingUtilities.invokeLater(() -> {
474+
closeAll(isContext
475+
? getOpened(TopComponent.getRegistry().getActivated())
476+
: Arrays.asList(WindowManagerImpl.getInstance().getEditorTopComponents()));
477+
});
494478
}
495479

496480
/** Closes all documents except given param, according to isContext flag
@@ -500,27 +484,24 @@ public void run() {
500484
* given
501485
*/
502486
public static void closeAllExcept (TopComponent tc, boolean isContext) {
503-
if (isContext) {
504-
List<TopComponent> tcs = getOpened(tc);
505-
506-
for(TopComponent curTC: tcs) {
507-
if( !Switches.isClosingEnabled(curTC) )
508-
continue;
509-
if (curTC != tc) {
510-
curTC.putClientProperty("inCloseAll", Boolean.TRUE);
511-
curTC.close();
512-
}
513-
}
514-
} else {
515-
TopComponent[] tcs = WindowManagerImpl.getInstance().getEditorTopComponents();
487+
// See closeAllDocuments.
488+
SwingUtilities.invokeLater(() -> {
489+
List<TopComponent> tcs = new ArrayList<>(isContext
490+
? getOpened(tc)
491+
: Arrays.asList(WindowManagerImpl.getInstance().getEditorTopComponents()));
492+
tcs.remove(tc);
493+
closeAll(tcs);
494+
});
495+
}
516496

517-
for(TopComponent curTC: tcs) {
518-
if( !Switches.isClosingEnabled(curTC) )
519-
continue;
520-
if (curTC != tc) {
521-
curTC.putClientProperty("inCloseAll", Boolean.TRUE);
522-
curTC.close();
523-
}
497+
private static void closeAll( Iterable<TopComponent> tcs ) {
498+
for (TopComponent curTC : tcs) {
499+
if( !Switches.isClosingEnabled(curTC) ) {
500+
continue;
501+
}
502+
curTC.putClientProperty("inCloseAll", Boolean.TRUE);
503+
if (!curTC.close()) {
504+
break;
524505
}
525506
}
526507
}

0 commit comments

Comments
 (0)