How to dispose of SWT Shells (and Dialogs)? - java

What is the proper way to dispose of SWT Shells? I have created some Dialogs with success following the template given in the Dialog API docs. The SWT API for Dialog says:
The basic template for a user-defined dialog typically looks something
like this:
public class MyDialog extends Dialog {
Object result;
public MyDialog (Shell parent, int style) {
super (parent, style);
}
public MyDialog (Shell parent) {
this (parent, 0); // your default style bits go here (not the Shell's style bits)
}
public Object open () {
Shell parent = getParent();
Shell shell = new Shell(parent, SWT.DIALOG_TRIM | SWT.APPLICATION_MODAL);
shell.setText(getText());
// Your code goes here (widget creation, set result, etc).
shell.open();
Display display = parent.getDisplay();
while (!shell.isDisposed()) {
if (!display.readAndDispatch()) display.sleep();
}
return result;
}
}
The Dialogs I have created do not have their own taskbar icon on Windows, as I would expect for a Dialog. I now want to create some Shells, which if I understand correctly will receive their own taskbar entry on Windows?
In contrast to the directions given in the above API docs, I have also seen an article which seems to suggest that having a while loop as shown in the API docs is the wrong way to do it. The article instead suggests disposing the Shell by using the close event listener.
What is the correct way to dispose of SWT Shells (and while were on the topic, Dialogs as well)?

Hopefully I can help explain what's going on here.
Dialog is basically just a convenient class to subclass, because Shell itself is not supposed to be subclassed. You can create shells without using Dialog at all, if you want to. In this case, your MyDialog class is a class that others can use to open the same kind of dialog repeatedly.
This piece of code drives the SWT event loop as long as the shell is open (clicking the close button on the shell disposes the shell by default):
while (!shell.isDisposed()) {
if (!display.readAndDispatch()) display.sleep();
}
You have to call Display.readAndDispatch periodically to keep your SWT application from "locking up". Basically it causes all of the incoming events from the operating system (keyboard and mouse events, repaint events, etc.) to be correctly processed by your application. readAndDispatch basically takes an event from the application's event queue and invokes the correct listeners. In an Eclipse RCP application, the workbench is normally responsible for "pumping" the event loop and you don't have to mess with it. Here's a little more info about the event loop.
The purpose of "manually" pumping the event loop in this context is to prevent MyDialog.open from returning while the shell is not disposed, but still keep the application from "hanging". If your MyDialog.open method tried to wait for the shell to be disposed, but it didn't pump the event loop, your application would "lock up" because without the event loop running, there's no way for the shell to ever be notified that it should be disposed!
You can create shells without using this pattern. Here's an example of a very simple SWT application that opens a ton of shells all at once and keeps running as long as at least one of them is still open (I've omitted the package declaration and imports):
public class Shells {
private static int numDisposals = 0;
public static void main(String[] args) {
Display d = Display.getDefault();
for (int i = 0; i < 5; i++) {
Shell s = new Shell(d);
s.open();
s.addDisposeListener(new DisposeListener() {
#Override
public void widgetDisposed(DisposeEvent arg0) {
numDisposals++;
}
});
}
while (numDisposals < 5) {
while (!d.readAndDispatch()) {
d.sleep();
}
}
}
}
Note that I add a DisposeListener to each shell so I can take some action when the shell is closed. You can also use IShellListener to listen more directly for the close event and even actually prevent it (which you might want to do if the shell contains unsaved work, for example). Here's an annoying modification to the first program that starts 5 shells and randomly prevents you from closing them:
public class Shells {
private static Random r = new Random();
private static int numDisposals = 0;
public static void main(String[] args) {
Display d = Display.getDefault();
for (int i = 0; i < 5; i++) {
Shell s = new Shell(d);
s.open();
s.addShellListener(new ShellAdapter() {
#Override
public void shellClosed(ShellEvent e) {
boolean close = r.nextBoolean();
if (close) {
System.out.println("Alright, shell closing.");
} else {
System.out.println("Try again.");
}
e.doit = close;
}
});
s.addDisposeListener(new DisposeListener() {
#Override
public void widgetDisposed(DisposeEvent arg0) {
numDisposals++;
}
});
}
while (numDisposals < 5) {
while (!d.readAndDispatch()) {
d.sleep();
}
}
}
}
Hopefully this has helped make things more clear!
Edited to add: I'm not totally sure why you aren't getting a windows taskbar item for your shell, but I suspect it has something to do with the style flags you're passing into the shell's constructor. The shells in my example have no style flags and they all get a taskbar icon.

Related

Closing a frame in Java and opening another one, with the swt library from eclipse

I'm currently making a reading app in Java, and this is my main menu.
What I want is that when I press the bottom button after selecting a book another window with the book opens. What I did now is a function that will open the other window while closing the one I'm currently in to free a little bit of memory.
I first close the current window after retrieving the necessary information from it (the index of the book) like this:
btnOuvrirLeLivre.addSelectionListener(new SelectionAdapter() {
#Override
public void widgetSelected(SelectionEvent e) {
int index = list.getSelectionIndex();
LiseuseController controller = new LiseuseController(null, null);
parent.dispose();
controller.viewBookController(index);
}
});
(I'm using the MVC method for my project), parent is just the composite used to open the frame.
public void viewBookController(int index) {
Display displayBook = new Display();
Shell shellBook = new Shell(displayBook);
shellBook.setLayout(new GridLayout(1, false));
index += 250;
LiseuseView lecture = new LiseuseView(shellBook, SWT.NONE, index);
shellBook.pack();
shellBook.open();
while(!shellBook.isDisposed()) {
if(!displayBook.readAndDispatch())
displayBook.sleep();
}
displayBook.dispose();
}
The index is just the book's number in my database, everything should be fine but I get this error when I do this after pressing the button to open the book:
Exception in thread "main" org.eclipse.swt.SWTException: Invalid thread access
at org.eclipse.swt.SWT.error(SWT.java:4875)
at org.eclipse.swt.SWT.error(SWT.java:4790)
at org.eclipse.swt.SWT.error(SWT.java:4761)
at org.eclipse.swt.widgets.Display.checkDisplay(Display.java:824)
at org.eclipse.swt.widgets.Display.create(Display.java:887)
at org.eclipse.swt.graphics.Device.<init>(Device.java:126)
at org.eclipse.swt.widgets.Display.<init>(Display.java:563)
at org.eclipse.swt.widgets.Display.<init>(Display.java:554)
at Controller.LiseuseController.viewBookController(LiseuseController.java:133)
at View.LiseuseHome$4.widgetSelected(LiseuseHome.java:81)
at org.eclipse.swt.widgets.TypedListener.handleEvent(TypedListener.java:252)
at org.eclipse.swt.widgets.EventTable.sendEvent(EventTable.java:89)
at org.eclipse.swt.widgets.Display.sendEvent(Display.java:4209)
at org.eclipse.swt.widgets.Widget.sendEvent(Widget.java:1037)
at org.eclipse.swt.widgets.Display.runDeferredEvents(Display.java:4026)
at org.eclipse.swt.widgets.Display.readAndDispatch(Display.java:3626)
at Controller.LiseuseController.viewController(LiseuseController.java:126)
at Main.Main.main(Main.java:59)
(LiseuseController.java:133) contains "Display displayBook = new Display();"
And (LiseuseController.java:126) contains
if(!display.readAndDispatch())
display.sleep();
This is from the other function used to open the first window.
I don't really understand what is causing this error and I can't just put one in "visible" and the other one "invisible" like if I'm using Jframe because the 2 windows are on 2 different .java files.
Do you have any idea on how to fix this?
From the API documentation of org.eclipse.swt.widgets.Display
Applications which are built with SWT will almost always require only
a single display. In particular, some platforms which SWT supports
will not allow more than one active display. In other words, some
platforms do not support creating a new display if one already exists
that has not been sent the dispose() message.
You should therefore adapt your application to use only one instance of Display. Since the class LiceuseController already manages opening the initial window and the supplementary window(s), it seems fitting for it to manage an instance of Display for both uses.
This instance should be created at the application's start, maintained by the LiceuseController class and finally disposed when the application shuts down.
Another problem is that none of the methods in LiceuseController actually returns. As you can see from the stack trace, Display.readAndDispatch from LiseuseController.viewController is still active when you are creating the new window. I guess you also want to reopen the original window when the supplementary window is closed. Opening and closing windows in this manner will, however, endlessly increase your call stack until you end up with a StackOverflowException.
Instead, the LiceuseController should be able to create windows without outside interference. Therefore, instead of actively calling a method in the controller class to open another window, the listener should only tell the controller what window it should open next when the current window was closed.
An example could look like
public enum WindowType {
MAIN, BOOK, NONE
}
public class LiceuseController {
Display display;
WindowType nextToOpen = WindowType.MAIN;
public LiceuseController() {
display = new Display();
}
public void setNextToOpen(WindowType value) {
nextToOpen = value;
}
public void run() {
for (boolean run = true; run;) {
switch(nextToOpen) {
case MAIN:
viewController();
break;
case BOOK:
viewBookController();
break;
case NONE:
run = false;
break;
default:
throw new RuntimeException("unexpected enum constant");
}
}
/*
* Depending on how you want to manage the instance of this class,
* you could also extract this into a separate method.
*/
display.dispose();
}
private void viewController() {
// open main window using 'display'
}
private void viewBookController() {
// open book window using 'display'
}
}
so that a listener only needs to call LiceuseController.setNextToOpen and then close the current window. This will cause either viewController or viewBookController to return after which the loop will reenter and open the requested window. To shut down the application, call setNextToOpen with WindowType.NONE.

Codename one new gui builder-back command from EVERY form navigation

I am navigating between forms in the NEW GUI builder. The old one had a back button on every form by default.
How do I enable the back button on new gui builder in every form, every time i navigate in a new form? Tried through constants in theme.res. It is still not enabled by default.
Furthermore, is the method "new form1.show" the best way to navigate between forms ? (see code)
Assuming name files:
Main.java, myapplication.java, Form1 ,Form2 ,Form3
Code for navigation, assuming names button1 and Form3:
public void onbutton1ActionEvent(com.codename1.ui.events.ActionEvent ev) {
new Form3().show();
}
Back command from old gui builder, not working here:
public Form showForm(String resourceName, Command sourceCommand) {
try {
Form f = (Form)formNameToClassHashMap.get(resourceName).newInstance();
Form current = Display.getInstance().getCurrent();
if(current != null && isBackCommandEnabled() && allowBackTo(resourceName)) {
f.putClientProperty("previousForm", current);
setBackCommand(f, new Command(getBackCommandText(current.getTitle())) {
public void actionPerformed(ActionEvent evt) {
back(null);
}
});
}
if(sourceCommand != null && current != null && current.getBackCommand() == sourceCommand) {
f.showBack();
} else {
f.show();
}
return f;
} catch(Exception err) {
err.printStackTrace();
throw new RuntimeException("Form not found: " + resourceName);
}
}
I've tried:
form.setBackCommand(cmd);
public Command setBackCommand(String title, ActionListener<ActionEvent> listener)
public void setBackCommand(Command cmd)
public Command setBackCommand(String title, BackCommandPolicy policy, ActionListener<ActionEvent> listener)
public void setBackCommand(Command cmd, BackCommandPolicy policy)
boolean onBack() {
return true;
}
https://www.codenameone.com/blog/toolbar-back-easier-material-icons.html
on main.java and myapplication.java did not accept the commands.
Form3.getToolbar().setBackCommand("", e -> Form3.showBack());
althouth is should not work only for form3, but every form.
Did not work either. Putting "back command" on every sidemenu would not be the ideal solution, because we might be navigating to each form from different forms.
EXTRA:
Is there a way to enable global toolbar and global commands for all forms, so i do not copy paste the toolbar code for each new form? If not answered here, i might make a new thread.
Thanks.
The old GUI builder handled navigation as it was designed at a time when Nokia was the worlds leader in the mobile phone industry and a 4in device was considered large. Back then we assumed the UI was simpler for each form and the navigation was the hard part.
This changed. But the bigger problem for most developers was the concept of stateless navigation which triggered a lot of issues both in design and functionality.
The new GUI builder doesn't include any navigation code or any global code. Each form stands on its own.
Having said that you can implement your own state machine by just keeping form instances and showing the form you want to navigate to e.g.:
public static class Controller {
private static Form1 f1;
private static Form2 f2;
public static void showF1() {
if(f1 == null) f1 = new Form1();
f1.show();
}
// etc...
}
I used static context for simplicity but you can implement your own strategy. Notice that you can also insert global logic here e.g. add the toolbar as a function like:
private static void initForm(Form f) {
// add global commands to the toolbar
}
Alternatively you can derive all the forms from a common base class as the new GUI builder doesn't restrict your inheritance.

Is there a way to prevent an IWorkbenchWindow from closing?

So I have my IWorkbenchWiondow object, window.
I add this listener to it:
window.addPageListener(new IPageListener()
{
#Override
public void pageOpened(IWorkbenchPage page)
{
// method stub
}
/**
* Whenever the user tries to close the workbench window, this method gets called.
*/
#Override
public void pageClosed(IWorkbenchPage page)
{
if (MessageDialog.openQuestion(page.getWorkbenchWindow().getShell(), "Question", "Do you really want to close the application?"))
{
// YES, then no problem, close
return;
}
else
{
// NO
System.out.println("Now what?");
}
}
#Override
public void pageActivated(IWorkbenchPage page)
{
// method stub
}
});
How can I stop the window from closing if the user says No?
Or how can I achieve the same end result?
As #david said, the IWorkbenchListener has a preShutDown event that allows to veto the shutdown of the entire workbench by returning false.
The workbench is shut down when the last workbench window is closed or through actions such as File > Exit.
If you would like to prevent a single IWorkbenchWindow from being closed, you need to add a close listener to the shell that represents the workbench window.
For example:
Shell shell = window.getShell();
shell.addListener( SWT.Close, new Listener() {
public void handleEvent( Event event ) {
MessageBox messageBox = new MessageBox( parentShell, SWT.APPLICATION_MODAL | SWT.YES | SWT.NO );
messageBox.setText( "Confirmation" );
messageBox.setMessage( "Close the window?" );
event.doit = messageBox.open() == SWT.YES;
}
} );
Setting the doit flag to false will prevent the shell from being closed/disposed.
Caveat: I've only given this technique a minimal test and it appears to work as expected.
From the IWorkBenchWindow get an IWorkbench object.
Add an IWorkbenchListener to the workbench object.
The listener has a preShutdown method that should allow you to veto the close.

SWT: Influence the detection of a Drag&Drop gesture (or: how to query the keyboard)

I am at an SWT application where one can rearrange controls within a shell (or any Composite for that matter) via drag&drop. That's basically no problem, DragSources and DropTargets are all in place and listeners attached accordingly. I even implemented a custom Transfer type for the sake of exercise. Pretty straightforward.
But now the requirement is that a drag should only be initiated, if the ALT key is pressed while the drag gesture is performed, otherwise nothing should be done. (The ALT key is an example, could be CTRL as well.)
So far, I see or have thought about the following approaches. All of them either don't work or are ugly.
\1. Intercept and cancel the DragDetect event
The idea is to cancel the event if the ALT key is not pressed with event.doit = false.
lblPos.addListener(SWT.DragDetect, new Listener() {
public #Override void handleEvent(Event event) {
if ((event.stateMask & SWT.ALT) == 0)
event.doit = false; // XXX: doit will not be evaluated
}
});
However, that doesn't work. The doit flag is apparently not evaluated.
\2. Intercept and cancel the DND.DragStart event.
class RowDragListener implements DragSourceListener {
public #Override void dragStart(DragSourceEvent event) {
if (/* ALT key not pressed */)
event.doit = false;
}
...
}
This has the opposite problem of appraoch 1. While the doit flag is properly evaluated and thus suitable to cancel the drag, there is no stateMask in the event that can be inspected for modifier keys. So the question arises, how can I query the keyboard directly (without installing KeyUp/Down event handlers)? What is the current up/down state of the ALT key?
\3. Combine 1 and 2
Inspect the stateMask in the DragDetect event, store the result somewhere, then react accordingly in the DND.DragStart event. This shouldn't be too hard, but I think it's ugly and should not be done this way. Instead of DragDetect, KeyUp/Down events could be captured and the last known state of the ALT key be stored.
\4. Override Control.dragDetect(Event) or Control.dragDetect(MouseEvent)
These methods ultimately create DragDetect events if they see the conditions for it fulfilled.
Check the event's stateMask and invoke the overridden method from the super class only if the desired modifier key is signalled. Problem here is, from the documentation it is not clear if this is the only code path that is treaded upon a drag gesture. In fact, these two methods are independent from each other (they don't invoke each other), so it's not even clear which one to override. These methods already are two separate ways to initiate a drag gesture. Who knows how many more ways are there? Overriding them all would be error prone, if possible at all, and certainly not clean.
So my questions are:
1. How would you do that? Any other ideas?
2. If approach 2 seems the most reasonable, how is the keyboard queried without resorting to event handlers?
(Sorry for the formatting of this post, i seem to be unable to grasp the syntax. Or maybe it's not my fault, who knows.)
UPDATE: There's one thing to note, which i noticed during the implementation. On Windows, ALT-Drag&Drop has the specific meaning of a link operation (as opposed to move or copy; cmp. DND.DROP_* constants). That's why, if you choose to use the ALT key in a similar fashion, be advised to include the following line at every reasonable occasion in the DropTargetListener.
if (event.detail == DND.DROP_LINK) event.detail = DND.DROP_MOVE;
I have this in the dragEnter, dragOver and dragOperationChanged listener methods and this works quite fine.
You can listen to SWT.DragDetect event, check the state mask and create the drag source only if conditions are met. Then pass the event to the newly created drag source by calling notifyListeneres(). After drag finishes the drag source has to be disposed.
Here is a snippet where drag is initiated only if alt is pressed, and uses text as transfer:
public static void main(String[] args) {
final Display display = new Display();
final Shell shell = new Shell(display);
shell.setLayout(new GridLayout());
shell.addListener(SWT.DragDetect, new Listener() {
#Override
public void handleEvent(Event event) {
if ((event.stateMask & SWT.ALT) != 0) {
final DragSource dragSource = new DragSource(shell, DND.DROP_MOVE);
dragSource.addDragListener(new DragSourceAdapter(){
#Override
public void dragFinished(DragSourceEvent event) {
dragSource.dispose();
}
#Override
public void dragSetData(DragSourceEvent event) {
event.data = "text";
}
});
dragSource.setTransfer(new Transfer[]{TextTransfer.getInstance()});
dragSource.notifyListeners(SWT.DragDetect, event);
}
}
});
shell.open();
while (!shell.isDisposed()) {
if (!display.readAndDispatch()) {
display.sleep();
}
}
display.dispose();
}

FullScreen Swing Components Fail to Receive Keyboard Input on Java 7 on Mac OS X Mountain Lion

Update 12/21:
7u10 was recently released. Confirmed that:
The issue still persists
Thankfully, the workaround still functions!
Update 11/7:
And we have a workaround!
Leonid Romanov from Oracle on the openjdk.java.net mailing list provided some insight as to what's going on:
Well, although I'm not 100% sure yet, but it looks like when we enter full screen some other window becomes the first responder, hence the beep. Could you please try the following workaround: after calling setFullScreenWindow() on a frame, call setVisible(false) followed by setVisible(true). This, in theory, should restore the correct first responder.
The snippet of code that seems to work is simply:
dev.setFullScreenWindow(f);
f.setVisible(false);
f.setVisible(true);
I have updated the sample code with the ability to toggle this fix on and off; it is required every time a window enters fullscreen.
In the larger context of my more complex application, I am still running into keyboard focus issues on subcomponents within the fullscreen window, where a mouse click causes my window to lose focus. (I'm guessing it's going to the undesired first responder window referenced above.) I'll report back when I have more information about this case - I cannot reproduce it in the smaller sample yet.
Update 10/31:
Major update to the sample code:
Includes toggle between FullScreen exclusive and Lion-style FullScreen modes
Listens to the KeyboardFocusManager to display the hierarchy for the currently focused component
Uses both input maps and KeyListeners to try to capture input
Also did some more digging with coworkers to try to isolate issues:
On one front, we tried overriding some methods in RT.jar to see if there were problems with the way the screen device is being selected. Also tried were the entry points to the Toolkit.beep() functionality to see if the alert sounds were coming from the Java side - appears not.
On another front, it was clear that not even the native side is receiving keyboard events. A coworker attributes this to a switch from an AWTView to a NSWindow in 7u6.
A selection of existing Oracle bugs has been found, which you can look up here:
8000276 : [macosx] graphicsDevice.setFullScreenWindow(frame) crashes JVM
8000430 : [macosx] java.awt.FileDialog issues on macosx
7175707 : [macosx] PIT: 8 b43 Not running on AppKit thread issue again
Update 10/26:
Thanks to the comment by #maslovalex below regarding an Applet working on 7u5, I went back and painstakingly examined compatibility with the JDK versions for OSX:
10.7.1 with 7u4: Fullscreen Works!
10.7.1 with 7u5: Fullscreen Works!
10.7.5 with 7u5: Fullscreen Works!
10.7.5 with 7u6: Fullscreen Breaks :(
Combined with the other tests noted elsewhere, it's clear there was an issue introduced with 7u6 that remains in 7u7 and 7u9, and it affects both Lion 10.7 and Mountain Lion 10.8.
7u6 was a major milestone release, bringing full support of the JRE and JDK to Mac OS X and also including Java FX as part of the distribution. Further info is available in the Release Notes and the Roadmap. It's not tremendously surprising that such an issue could crop up as support shifts to Java FX.
The question becomes:
Will Oracle fix this in a near-term release of the JDK? (If you have links to existing bugs, please include them here.)
Is a workaround possible in the interim?
Other updates from today:
I incorporated the Apple extensions approach to fullscreen mode as an alternate path of exploration (updated sample code pending cleanup). The good news: input works! The bad news: there really don't seem to be any kiosking/isolation options.
I tried killing the Dock - directly or with an App - as I understand the Dock is responsible for Command-Tab app switching, Mission Control, and Launch Pad, only to find out that it's responsible for the handling of fullscreen apps as well! As such, the Java calls become non-functional and never return.
If there's a way to disable Command-Tab (and Mission Control and Launchpad and Spaces) without affecting the Dock's fullscreen handling, that would be extremely useful. Alternatively, one can try to remap certain keys such as Command, but that will affect the ability to use that modifier elsewhere in the program and the system itself (not exactly ideal, when you need to Command-C to copy some text).
I've had no luck with KeyListeners (I'm not receiving any callbacks), but I have a few more options to try.
Based on a coworker's suggestion, I tried ((sun.lwawt.macosx.LWCToolkit)Toolkit.getDefaultToolkit()).isApplicationActive() via reflection. The idea was that it:
is a native method with the comment "Returns true if the application (one of its windows) owns keyboard focus.". Calls to this method were added in CPlatformWindow.java in the past few months related to focus logic. If it returns false in your test code, it's probably part of the problem.
Unfortunately, everywhere I checked it, the method returned true. So even according to the low level system, my windows should have keyboard focus.
My previous optimism regarding the JAlbum fix has been dashed. The developer posted a response on their forum that explains how they simply removed proper fullscreen support on OS X while running Java 7. They have a bug into Oracle (and I'm hoping to get the bug number).
Update 10/25:
I have now also tried Java 7u9 on Lion 10.7.4 and have seen the exact same issue, so it's JDK- not OS-specific.
The core question has become whether you can embed in a fullscreen window core Swing Components that have default handling for keyboard input (JTextField/JTextArea or even editable combo boxes) and expect them to behave normally (without having to resort to rebuilding their basic key bindings manually). Also in question is whether other stalwarts of windowed layouts, such as using tab for focus traversal, should work.
The ideal goal would be to have the ability take a windowed Swing app with all of its buttons, tabs, fields, etc. and run it in fullscreen exclusive/kiosk mode with most functionality intact. (Previously, I have seen that Dialog pop ups or ComboBox drop downs do not function in fullscreen on Java 6 on OS X, but other Components behave fine.)
I'll be looking into the eawt FullScreen capabilities, which will be interesting if they support kiosk lock down options, such as eliminating Command-Tab application switching.
Original Question:
I have a Swing app that for years has supported FullScreen (exclusive) mode on Mac OS X up through Java 6. I've been doing compatibility testing with the latest Mountain Lion release (10.8.2 Supplemental) and Oracle's JDK 7 and noticed a glaring issue while in that mode: mouse movement and clicks work fine, but keyboard input is not delivered to the components.
(I've narrowed this down in a test case below to not being able to type into a simple JTextField while in fullscreen mode.)
One symptom is that each key press results in a system beep, as if the OS is disallowing keyboard events from being delivered to the application.
Separately, my application has an exit hook installed, and the Command-Q combo will trigger that hook - it's clear that the OS is listening to standard key combos.
I have tested this separately on three different Macs with various installs:
On Apple Java 6u35 and 6u37: both windowed and fullscreen modes receive input.
On Oracle Java 7u7 and 7u9: windowed mode works as expected while fullscreen has the symptoms above.
This may have been previously reported:
Java Graphics Full Screen Mode not Registering Keyboard Input.
However, that question is not specific about the Java version or platform.
Additional searching has turned up a separate fullscreen option introduced in Lion:
Fullscreen feature for Java Apps on OSX Lion.
I have yet to try using this approach, as keyboard input seems integral to the target use cases of FullScreen Exclusive mode, such as games.
There is some mention in the JavaDoc for this mode that input methods might be disabled. I tried to call the suggested Component.enableInputMethods(false), but it seemed to have no effect.
I'm somewhat optimistic that there's a solution to this issue based on an entry in the release notes of a Java app I came across (JAlbum). A stated fix for 10.10.6: "Keyboard support wasn't working when running the full screen slide show on Mac and Java 7"
My test case is below. It is lightly modified from the second example in this issue (which, unmodified, also exhibits my problem): How to handle events from keyboard and mouse in full screen exclusive mode in java?
In particular, it adds a button to toggle fullscreen.
import java.lang.reflect.*;
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import java.beans.*;
/** #see https://stackoverflow.com/questions/13064607/ */
public class FullScreenTest extends JPanel {
private GraphicsDevice dev = GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice();
private JFrame f = new JFrame("FullScreenTest");
private static final String EXIT = "Exit";
private Action exit = new AbstractAction(EXIT) {
#Override
public void actionPerformed(ActionEvent e) {
Object o = dev.getFullScreenWindow();
if(o != null) {
dev.setFullScreenWindow(null);
}
f.dispatchEvent(new WindowEvent(f, WindowEvent.WINDOW_CLOSING));
}
};
private JButton exitBTN = new JButton(exit);
private JTextField jtf = new JTextField("Uneditable in FullScreen with Java7u6+ on Mac OS X 10.7.3+");
private JLabel keystrokeLabel = new JLabel("(Last Modifier+Key Pressed in JTextField)");
private JLabel jtfFocusLabel = new JLabel("(JTextField Focus State)");
private JLabel focusLabel = new JLabel("(Focused Component Hierarchy)");
private JCheckBox useOSXFullScreenCB = new JCheckBox("Use Lion-Style FullScreen Mode");
private JCheckBox useWorkaroundCB = new JCheckBox("Use Visibility Workaround to Restore 1st Responder Window");
private static final String TOGGLE = "Toggle FullScreen (Command-T or Enter)";
private Action toggle = new AbstractAction(TOGGLE) {
#Override
public void actionPerformed(ActionEvent e) {
Object o = dev.getFullScreenWindow();
if(o == null) {
f.pack();
/**
* !! Neither of these calls seem to have any later effect.
* One exception: I have a report of a
* Mini going into an unrecoverable black screen without setVisible(true);
* May be only a Java 6 compatibility issue. !!
*/
//f.setVisible(true);
//f.setVisible(false);
if(!useOSXFullScreenCB.isSelected()) {
// No keyboard input after this call unless workaround is used
dev.setFullScreenWindow(f);
/**
* Workaround provided by Leonid Romanov at Oracle.
*/
if(useWorkaroundCB.isSelected()) {
f.setVisible(false);
f.setVisible(true);
//Not necessary to invoke later...
/*SwingUtilities.invokeLater(new Runnable() {
public void run() {
f.setVisible(false);
f.setVisible(true);
}
});*/
}
}
else {
toggleOSXFullscreen(f);
}
}
else {
dev.setFullScreenWindow(null);
f.pack();
f.setVisible(true);
}
isAppActive();
}
};
private JButton toggleBTN = new JButton(toggle);
public FullScreenTest() {
// -- Layout --
this.setLayout(new BoxLayout(this, BoxLayout.PAGE_AXIS));
exitBTN.setAlignmentX(JComponent.CENTER_ALIGNMENT);
exitBTN.setMaximumSize(new Dimension(Short.MAX_VALUE, 50));
this.add(exitBTN);
jtf.setAlignmentX(JComponent.CENTER_ALIGNMENT);
jtf.setMaximumSize(new Dimension(Short.MAX_VALUE, Short.MAX_VALUE));
this.add(jtf);
keystrokeLabel.setAlignmentX(JComponent.CENTER_ALIGNMENT);
keystrokeLabel.setMaximumSize(new Dimension(Short.MAX_VALUE, 50));
keystrokeLabel.setHorizontalAlignment(SwingConstants.CENTER);
keystrokeLabel.setForeground(Color.DARK_GRAY);
this.add(keystrokeLabel);
jtfFocusLabel.setAlignmentX(JComponent.CENTER_ALIGNMENT);
jtfFocusLabel.setMaximumSize(new Dimension(Short.MAX_VALUE, 50));
jtfFocusLabel.setHorizontalAlignment(SwingConstants.CENTER);
jtfFocusLabel.setForeground(Color.DARK_GRAY);
this.add(jtfFocusLabel);
focusLabel.setAlignmentX(JComponent.CENTER_ALIGNMENT);
focusLabel.setMaximumSize(new Dimension(Short.MAX_VALUE, 50));
focusLabel.setHorizontalAlignment(SwingConstants.CENTER);
focusLabel.setForeground(Color.DARK_GRAY);
this.add(focusLabel);
useOSXFullScreenCB.setAlignmentX(JComponent.CENTER_ALIGNMENT);
useOSXFullScreenCB.setMaximumSize(new Dimension(Short.MAX_VALUE, 50));
useOSXFullScreenCB.setHorizontalAlignment(SwingConstants.CENTER);
this.add(useOSXFullScreenCB);
useWorkaroundCB.setAlignmentX(JComponent.CENTER_ALIGNMENT);
useWorkaroundCB.setMaximumSize(new Dimension(Short.MAX_VALUE, 50));
useWorkaroundCB.setHorizontalAlignment(SwingConstants.CENTER);
this.add(useWorkaroundCB);
toggleBTN.setAlignmentX(JComponent.CENTER_ALIGNMENT);
toggleBTN.setMaximumSize(new Dimension(Short.MAX_VALUE, 50));
this.add(toggleBTN);
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.setResizable(false);
f.setUndecorated(true);
f.add(this);
f.pack();
enableOSXFullscreen(f);
// -- Listeners --
// Default BTN set to see how input maps respond in fullscreen
f.getRootPane().setDefaultButton(toggleBTN);
// Explicit input map test with Command-T toggle action from anywhere in the window
this.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(
KeyStroke.getKeyStroke(KeyEvent.VK_T, Toolkit.getDefaultToolkit().getMenuShortcutKeyMask()),
toggle.getValue(Action.NAME));
this.getActionMap().put(toggle.getValue(Action.NAME), toggle);
// KeyListener test
jtf.addKeyListener(new KeyAdapter() {
public void keyPressed(KeyEvent e) {
String ktext = "KeyPressed: "+e.getKeyModifiersText(e.getModifiers()) + "_"+ e.getKeyText(e.getKeyCode());
keystrokeLabel.setText(ktext);
System.out.println(ktext);
}
});
// FocusListener test
jtf.addFocusListener(new FocusListener() {
public void focusGained(FocusEvent fe) {
focused(fe);
}
public void focusLost(FocusEvent fe) {
focused(fe);
}
private void focused(FocusEvent fe) {
boolean allGood = jtf.hasFocus() && jtf.isEditable() && jtf.isEnabled();
jtfFocusLabel.setText("JTextField has focus (and is enabled/editable): " + allGood);
isAppActive();
}
});
// Keyboard Focus Manager
KeyboardFocusManager focusManager = KeyboardFocusManager.getCurrentKeyboardFocusManager();
focusManager.addPropertyChangeListener(new PropertyChangeListener() {
public void propertyChange(PropertyChangeEvent e) {
if (!("focusOwner".equals(e.getPropertyName()))) return;
Component comp = (Component)e.getNewValue();
if(comp == null) {
focusLabel.setText("(No Component Focused)");
return;
}
String label = comp.getClass().getName();
while(true) {
comp = comp.getParent();
if(comp == null) break;
label = comp.getClass().getSimpleName() + " -> " + label;
}
focusLabel.setText("Focus Hierarchy: " + label);
isAppActive();
}
});
}
/**
* Hint that this Window can enter fullscreen. Only need to call this once per Window.
* #param window
*/
#SuppressWarnings({"unchecked", "rawtypes"})
public static void enableOSXFullscreen(Window window) {
try {
Class util = Class.forName("com.apple.eawt.FullScreenUtilities");
Class params[] = new Class[]{Window.class, Boolean.TYPE};
Method method = util.getMethod("setWindowCanFullScreen", params);
method.invoke(util, window, true);
} catch (ClassNotFoundException e1) {
} catch (Exception e) {
System.out.println("Failed to enable Mac Fullscreen: "+e);
}
}
/**
* Toggle OSX fullscreen Window state. Must call enableOSXFullscreen first.
* Reflection version of: com.apple.eawt.Application.getApplication().requestToggleFullScreen(f);
* #param window
*/
#SuppressWarnings({"unchecked", "rawtypes"})
public static void toggleOSXFullscreen(Window window) {
try {
Class appClass = Class.forName("com.apple.eawt.Application");
Method method = appClass.getMethod("getApplication");
Object appInstance = method.invoke(appClass);
Class params[] = new Class[]{Window.class};
method = appClass.getMethod("requestToggleFullScreen", params);
method.invoke(appInstance, window);
} catch (ClassNotFoundException e1) {
} catch (Exception e) {
System.out.println("Failed to toggle Mac Fullscreen: "+e);
}
}
/**
* Quick check of the low-level window focus state based on Apple's Javadoc:
* "Returns true if the application (one of its windows) owns keyboard focus."
*/
#SuppressWarnings({"unchecked", "rawtypes"})
public static void isAppActive() {
try {
Class util = Class.forName("sun.lwawt.macosx.LWCToolkit");
Method method = util.getMethod("isApplicationActive");
Object obj = method.invoke(Toolkit.getDefaultToolkit());
System.out.println("AppActive: "+obj);
} catch (ClassNotFoundException e1) {
} catch (Exception e) {
System.out.println("Failed to check App: "+e);
}
}
public static void main(String[] args) {
System.out.println("Java Version: " + System.getProperty("java.version"));
System.out.println("OS Version: " + System.getProperty("os.version"));
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
FullScreenTest fst = new FullScreenTest();
if(!fst.dev.isFullScreenSupported()) {
System.out.println("FullScreen not supported on this graphics device. Exiting.");
System.exit(0);
}
fst.toggle.actionPerformed(null);
}
});
}
}
This is because the component to which you added the other has now lost focus, you can fix this by either:
calling requestFocus() on the component instance to which you add KeyBindings
or
alternatively use JComponent.WHEN_IN_FOCUSED_WINDOW with KeyBindings:
component.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke(KeyEvent.VK_Q, 0),
"doSomething");
component.getActionMap().put("doSomething",
anAction);
Reference:
How to Use Key Bindings
Instead, use key bindings, as shown in this FullScreenTest. Also, consider a DocumentListener, shown here, for text components.
I think I finally found a solution, registering click listeners against to the JFrame itself. (This is a class which extends JFrame, hence all the "this" references.)
/**
* Toggles full screen mode. Requires a lot of references to the JFrame.
*/
public void setFullScreen(boolean fullScreen){
GraphicsEnvironment env = GraphicsEnvironment.getLocalGraphicsEnvironment();
GraphicsDevice dev = env.getDefaultScreenDevice();//Gets the main screen
if(!fullScreen){//Checks if a full screen application isn't open
this.dispose();//Restarts the JFrame
this.setVisible(false);
this.setResizable(true);//Re-enables resize-ability.
this.setUndecorated(false);//Adds title bar back
this.setVisible(true);//Shows restarted JFrame
this.removeMouseListener(macWorkAround);
this.pack();
this.setExtendedState(this.getExtendedState()|JFrame.MAXIMIZED_BOTH);//Returns to maximized state
this.fullScreen = false;
}
else{
this.dispose();//Restarts the JFrame
this.setResizable(false);//Disables resizing else causes bugs
this.setUndecorated(true);//removes title bar
this.setVisible(true);//Makes it visible again
this.revalidate();
this.setSize(Toolkit.getDefaultToolkit().getScreenSize());
try{
dev.setFullScreenWindow(this);//Makes it full screen
if(System.getProperty("os.name").indexOf("Mac OS X") >= 0){
this.setVisible(false);
this.setVisible(true);
this.addMouseListener(macWorkAround);
}
this.repaint();
this.revalidate();
}
catch(Exception e){
dev.setFullScreenWindow(null);//Fall back behavior
}
this.requestFocus();
this.fullScreen = true;
}
}
private MouseAdapter macWorkAround = new MouseAdapter(){
public void mouseClicked(MouseEvent e){
MainGUI.this.setVisible(false);
MainGUI.this.setVisible(true);
}
};

Categories

Resources