Skip to content

Commit b147628

Browse files
committed
System File Chooser: block Swing input events (mouse, keyboard, etc.) while system file dialog is shown (issue #1100)
1 parent 42c6be1 commit b147628

3 files changed

Lines changed: 117 additions & 8 deletions

File tree

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ FlatLaf Change Log
55

66
- System File Chooser:
77
- macOS: `Cmd+A` (**Select All**) did not work in file dialog. (issue #1084)
8+
- Block Swing input events (mouse, keyboard, etc.) while system file dialog is
9+
shown. (issue #1100)
810
- macOS: Fixed missing close/iconify/maximize buttons on inactive window, if
911
system appearance is dark, but application appearance is light. (issue #1032)
1012

flatlaf-core/src/main/java/com/formdev/flatlaf/util/SystemFileChooser.java

Lines changed: 90 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
package com.formdev.flatlaf.util;
1818

19+
import java.awt.AWTEvent;
1920
import java.awt.Component;
2021
import java.awt.Dimension;
2122
import java.awt.EventQueue;
@@ -24,6 +25,9 @@
2425
import java.awt.SecondaryLoop;
2526
import java.awt.Toolkit;
2627
import java.awt.Window;
28+
import java.awt.event.ActionEvent;
29+
import java.awt.event.KeyEvent;
30+
import java.awt.event.MouseEvent;
2731
import java.io.File;
2832
import java.lang.reflect.InvocationTargetException;
2933
import java.lang.reflect.Method;
@@ -773,7 +777,8 @@ private int showDialogImpl( Component parent ) {
773777
}
774778

775779
private FileChooserProvider getProvider() {
776-
if( !FlatSystemProperties.getBoolean( FlatSystemProperties.USE_SYSTEM_FILE_CHOOSER, true ) )
780+
if( !FlatSystemProperties.getBoolean( FlatSystemProperties.USE_SYSTEM_FILE_CHOOSER, true ) ||
781+
SystemInfo.isProjector || SystemInfo.isWebswing || SystemInfo.isWinPE )
777782
return new SwingFileChooserProvider();
778783

779784
if( SystemInfo.isWindows_10_orLater && FlatNativeWindowsLibrary.isLoaded() )
@@ -810,14 +815,25 @@ public File[] showDialog( Window owner, SystemFileChooser fc ) {
810815
}
811816

812817
AtomicReference<String[]> filenamesRef = new AtomicReference<>();
818+
InputBlockingEventQueue queue = InputBlockingEventQueue.install();
813819

814-
// create secondary event look and invoke system file dialog on a new thread
815-
SecondaryLoop secondaryLoop = Toolkit.getDefaultToolkit().getSystemEventQueue().createSecondaryLoop();
816-
new Thread( () -> {
817-
filenamesRef.set( showSystemDialog( owner, fc ) );
818-
secondaryLoop.exit();
819-
}, "FlatLaf SystemFileChooser" ).start();
820-
secondaryLoop.enter();
820+
try {
821+
// create secondary event look and invoke system file dialog on a new thread
822+
SecondaryLoop secondaryLoop = Toolkit.getDefaultToolkit().getSystemEventQueue().createSecondaryLoop();
823+
new Thread( () -> {
824+
try {
825+
filenamesRef.set( showSystemDialog( owner, fc ) );
826+
} finally {
827+
secondaryLoop.exit();
828+
}
829+
}, "FlatLaf SystemFileChooser" ).start();
830+
831+
// enter secondary loop, which waits until system file dialog is closed
832+
secondaryLoop.enter();
833+
} finally {
834+
if( queue != null )
835+
queue.uninstall();
836+
}
821837

822838
// dispose dummy window to allow AWT to auto-shutdown
823839
if( dummyWindow != null )
@@ -870,6 +886,72 @@ private static boolean hasDisplayableWindow( Window owner ) {
870886
}
871887
return false;
872888
}
889+
890+
//---- class InputBlockingEventQueue ----------------------------------
891+
892+
/**
893+
* Used while system file dialog is displayed to block some Swing events.
894+
* This is similar to what Swing does when showing modal dialogs.
895+
* See class {@code java.awt.ModalEventFilter}.
896+
* <p>
897+
* Without this, it would be possible for the user to e.g. press some
898+
* shortcut keys during creation of system file dialog, which is done
899+
* on another thread, and open additional dialogs.
900+
* E.g. press Ctrl+O twice, could open two file dialogs.
901+
*/
902+
private static class InputBlockingEventQueue
903+
extends EventQueue
904+
{
905+
private static boolean problemLogged;
906+
907+
static InputBlockingEventQueue install() {
908+
InputBlockingEventQueue queue = new InputBlockingEventQueue();
909+
910+
try {
911+
Toolkit toolkit = Toolkit.getDefaultToolkit();
912+
toolkit.getSystemEventQueue().push( queue );
913+
914+
// check whether push() worked
915+
// (e.g. SWTSwing uses own event queue that does not support push())
916+
if( toolkit.getSystemEventQueue() != queue ) {
917+
logEventQueuePushProblem( null );
918+
return null;
919+
}
920+
921+
return queue;
922+
} catch( RuntimeException ex ) {
923+
// catch runtime exception from EventQueue.push()
924+
logEventQueuePushProblem( ex );
925+
return null;
926+
}
927+
}
928+
929+
private static void logEventQueuePushProblem( Throwable t ) {
930+
if( problemLogged )
931+
return;
932+
problemLogged = true;
933+
934+
LoggingFacade.INSTANCE.logSevere( "FlatLaf: Failed to push input-blocking event queue.", t );
935+
}
936+
937+
void uninstall() {
938+
super.pop();
939+
}
940+
941+
@Override
942+
protected void dispatchEvent( AWTEvent event ) {
943+
int eventID = event.getID();
944+
if( (eventID >= MouseEvent.MOUSE_FIRST && eventID <= MouseEvent.MOUSE_LAST) ||
945+
(eventID >= KeyEvent.KEY_FIRST && eventID <= KeyEvent.KEY_LAST) ||
946+
(eventID >= ActionEvent.ACTION_FIRST && eventID <= ActionEvent.ACTION_LAST) )
947+
{
948+
// System.out.println( "BLOCKED " + event );
949+
return;
950+
}
951+
952+
super.dispatchEvent( event );
953+
}
954+
}
873955
}
874956

875957
//---- class WindowsFileChooserProvider -----------------------------------

flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatSystemFileChooserTest.java

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@
2323
import java.awt.Frame;
2424
import java.awt.Point;
2525
import java.awt.Window;
26+
import java.awt.event.MouseEvent;
27+
import java.awt.event.MouseListener;
2628
import java.awt.event.WindowAdapter;
2729
import java.awt.event.WindowEvent;
2830
import java.awt.event.WindowFocusListener;
@@ -363,13 +365,17 @@ private static char mnemonic( String s ) {
363365
}
364366

365367
private void showWithOwner( Consumer<Window> showConsumer ) {
368+
System.out.println();
369+
366370
Window frame = SwingUtilities.windowForComponent( this );
367371
if( ownerFrameRadioButton.isSelected() )
368372
showConsumer.accept( frame );
369373
else if( ownerDialogRadioButton.isSelected() )
370374
new DummyModalDialog( frame, showConsumer ).setVisible( true );
371375
else
372376
showConsumer.accept( null );
377+
378+
System.out.println();
373379
}
374380

375381
private void currentDirChanged() {
@@ -539,6 +545,16 @@ static void printWindowEvent( WindowEvent e ) {
539545
System.out.println( typeStr + " source " + sourceStr + " opposite " + oppositeStr );
540546
}
541547

548+
private void addMouseListener( JButton button ) {
549+
button.addMouseListener( new MouseListener() {
550+
@Override public void mouseClicked( MouseEvent e ) { System.out.println( e ); }
551+
@Override public void mousePressed( MouseEvent e ) { System.out.println( e ); }
552+
@Override public void mouseReleased( MouseEvent e ) { System.out.println( e ); }
553+
@Override public void mouseEntered( MouseEvent e ) { System.out.println( e ); }
554+
@Override public void mouseExited( MouseEvent e ) { System.out.println( e ); }
555+
} );
556+
}
557+
542558
private void initComponents() {
543559
// JFormDesigner - Component initialization - DO NOT MODIFY //GEN-BEGIN:initComponents
544560
JLabel ownerLabel = new JLabel();
@@ -854,6 +870,15 @@ private void initComponents() {
854870
ownerButtonGroup.add(ownerDialogRadioButton);
855871
ownerButtonGroup.add(ownerNullRadioButton);
856872
// JFormDesigner - End of component initialization //GEN-END:initComponents
873+
874+
addMouseListener( openButton );
875+
addMouseListener( saveButton );
876+
addMouseListener( swingOpenButton );
877+
addMouseListener( swingSaveButton );
878+
addMouseListener( awtOpenButton );
879+
addMouseListener( awtSaveButton );
880+
addMouseListener( javafxOpenButton );
881+
addMouseListener( javafxSaveButton );
857882
}
858883

859884
// JFormDesigner - Variables declaration - DO NOT MODIFY //GEN-BEGIN:variables

0 commit comments

Comments
 (0)