JavaFX frame rate drop on Windows Server 2016 - performance

I am developing a trading app with a GUI and charting modules using JavaFX that has to be run on a server to be close to trading centers. While my app runs very smoothly on my home PC it is unresponsive on any dedicated server or VPS I have tried so far.
The trading app is too big to share the code here, so I made a small test app that allows to quantify the difference. It is showing the frame rate (thanks to What is the preferred way of getting the frame rate of a JavaFX application?) and if you click the button it simulates some GUI work.
The dedicated server specs are:
AMD Ryzen 5 3600X 6-Core Processor 3.80 GHz
16.0 GB RAM
64-bit Operating System, x64-based processor
Windows Server 2016 Standard
My home PC specs are:
AMD Ryzen 7 5700U with Radeon Graphics 1.80 GHz
16.0 GB RAM
64-bit Operating System, x64-based processor
Windows 10 Home
Running the test on my home PC:
Frame rate after starting: 60 per second.
Frame rate when simulation GUI work: 60 per second.
Running the test on the dedicated server:
Frame rate after starting: 52 per second.
Frame rate when simulation GUI work: 40 per second.
When doing the test with the app started simultaneously five times it pushes the frame rate down towards 30 frames per second on the server, while on the PC it stays at 60 per second.
Running the app with -Dprism.verbose=true shows that on the server the Microsoft Basic Render Driver is used what might cause the difference in performance:
What can I do to improve the performance of JavaFX on the server or if possible to make it run as smoothly as it is running on the home PC?
Edit: I figured out that running the app with -Dprism.order=sw as VM option enhances the performance on the server significantly. The test app is there now running at same FPS as on local PC.
Here is the code of the test app:
import javafx.animation.AnimationTimer;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.layout.BorderPane;
import javafx.stage.Stage;
public class SimpleFrameRateMeter extends Application {
private final long[] frameTimes = new long[100];
private int frameTimeIndex = 0;
private boolean arrayFilled = false;
#Override
public void start(Stage primaryStage) {
borderPane = new BorderPane();
runFrameRateMeter();
//add a button to start some GUI action
button = new Button("Start");
button.setOnAction(e -> {
if (button.getText().equals("Start")) {
button.setText("Running...");
doSomething();
}
});
borderPane.setLeft(button);
primaryStage.setScene(new Scene(borderPane, 250, 150));
primaryStage.show();
}
private BorderPane borderPane;
private Button button;
private Label label = new Label();
//some GUI work
private void doSomething() {
new Thread(() -> {
for (int i = 0; i < 5; i++) {
final int cnt = i;
Platform.runLater(() -> {
//Label label = new Label(String.valueOf(cnt));
label.setText(String.valueOf(cnt));
borderPane.setCenter(label);
});
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
Platform.runLater(() -> {
button.setText("Start");
});
}).start();
}
//Measure frame rates from https://stackoverflow.com/questions/28287398/what-is-the-preferred-way-of-getting-the-frame-rate-of-a-javafx-application
private void runFrameRateMeter() {
Label label = new Label();
borderPane.setTop(label);
AnimationTimer frameRateMeter = new AnimationTimer() {
#Override
public void handle(long now) {
long oldFrameTime = frameTimes[frameTimeIndex];
frameTimes[frameTimeIndex] = now;
frameTimeIndex = (frameTimeIndex + 1) % frameTimes.length;
if (frameTimeIndex == 0) {
arrayFilled = true;
}
if (arrayFilled) {
long elapsedNanos = now - oldFrameTime;
long elapsedNanosPerFrame = elapsedNanos / frameTimes.length;
double frameRate = 1_000_000_000.0 / elapsedNanosPerFrame;
label.setText(String.format("Current frame rate: %.3f", frameRate));
}
}
};
frameRateMeter.start();
}
public static void main(String[] args) {
launch(args);
}
}

Related

Audio performance with Javafx for Android (MediaPlayer and NativeAudioService)

I have created, with JavaFX, a game on desktop that works fine (20000 Java lines. As it is a game, the Real Time constraint is important (response time of player's actions).
The final aim is to run this application with Android. I have almost finished to "transfer the Java code" from PC to Android, even if I have encountered some real time trouble. I think almost all of them are solved now.
For instance, I have minimized the CPU time (consumption) of Shape or Rectangle.intersect(node1, node2) calls that are used for detecting impacts between two mobiles. Thus, the real time has been divided by 3. Great!
For testing this Android version, I use Eclipse + Neon2, JavaFX, JavaFXports + gluon and my phone (Archos Diamond S).
But, for Android phones, I had a real time problem related to the sounds that are generated with MediaPlayer and NativeAudioSrvice.
Yet, I have followed this advice that suggests the synchronous mode:
javafxports how to call android native Media Player
1st question:
Does it exist an asynchronous mode with this Mediaplayer class?I think that would solve this latency problem?
In practice, I have tried the asynchronous solution ... without success: the real time problem due to the audio generation with MediaPlayer stays: an audio generation costs from 50 ms to 80 ms whereas the main cyclic processing runs each 110 ms. Each audio generation can interfer with the main processing execution.
And, in each periodic task (rate: 110 ms), I can play several sounds like that. And, in a trace, there was up to six sound activations that take (together) about 300 ms (against the 110 ms of the main cyclic task )
QUESTION:
How to improve the performance of NativeAudio class (especially, the method play() with its calls that create the real time problem: setDataSource(...), prepare() and start() )?
THE SOLUTION
The main processing must be a "synchronized" method to be sure that this complete processing will be run, without any audio interruption.
More, each complete processing for generating a sound is under a dedicated thread, defined with a Thread.MIN_PRIORITY priority.
Now, the main processing is run each 110 ms and, when it begins, it cannot be disturbed by any audio generation. The display is very "soft" (no more jerky moving).
There is just a minor problem: when an audio seDataSource(), a start() or a prepare() method has begun, it seems to be that the next main processing shall wait the end of the method before beginning (TBC)
I hope this solution could help another people. It is applicable in any case of audio generations with MediaPlayer.
JAVA code of the solution
The main processing is defined like that:
public static ***synchronized*** void mainProcessing() {
// the method handles the impacts, explosions, sounds, movings, ... , in other words almost the entiere game .. in a CRITICAL SECTION
}
/****************************************************/
In the NativeAudio class that implements "NativeAudioService":
#Override
public void play() {
if (bSon) {
Task<Void> taskSound = new Task<Void>() {
#Override
protected Void call() throws Exception {
generateSound();
return null;
}};
Thread threadSound = new Thread(taskSound);
threadSound.setPriority(Thread.MIN_PRIORITY);
threadSound.start();
}
}
/****************************************************/
private void generateSound() {
currentPosition = 0;
nbTask++;
noTask = nbTask;
try {
if (mediaPlayer != null) {
stop();
}
mediaPlayer = new MediaPlayer();
AssetFileDescriptor afd = FXActivity.getInstance().getAssets().openFd(audioFileName);
mediaPlayer.setDataSource(afd.getFileDescriptor(), afd.getStartOffset(), afd.getLength());
mediaPlayer.setAudioStreamType(AudioManager.STREAM_RING);
float floatLevel = (float) audioLevel;
mediaPlayer.setVolume(floatLevel, floatLevel);
mediaPlayer.setOnCompletionListener(new OnCompletionListener() {
#Override
public void onCompletion(MediaPlayer mediaPlayer) {
if (nbCyclesAudio >= 1) {
mediaPlayer.start();
nbCyclesAudio--;
} else {
mediaPlayer.stop();
mediaPlayer.release(); // for freeing the resource - useful for the phone codec
mediaPlayer = null;
}
}
});
mediaPlayer.prepare();
mediaPlayer.start();
nbCyclesAudio--;
} catch (IOException e) {
}
}
I've changed a little bit the implementation you mentioned, given that you have a bunch of short audio files to play, and that you want a very short time to play them on demand. Basically I'll create the AssetFileDescriptor for all the files once, and also I'll use the same single MediaPlayer instance all the time.
The design follows the pattern of the Charm Down library, so you need to keep the package names below.
EDIT
After the OP's feedback, I've changed the implementation to have one MediaPlayer for each audio file, so you can play any of them at any time.
Source Packages/Java:
package: com.gluonhq.charm.down.plugins
AudioService interface
public interface AudioService {
void addAudioName(String audioName);
void play(String audioName, double volume);
void stop(String audioName);
void pause(String audioName);
void resume(String audioName);
void release();
}
AudioServiceFactory class
public class AudioServiceFactory extends DefaultServiceFactory<AudioService> {
public AudioServiceFactory() {
super(AudioService.class);
}
}
Android/Java Packages
package: com.gluonhq.charm.down.plugins.android
AndroidAudioService class
public class AndroidAudioService implements AudioService {
private final Map<String, MediaPlayer> playList;
private final Map<String, Integer> positionList;
public AndroidAudioService() {
playList = new HashMap<>();
positionList = new HashMap<>();
}
#Override
public void addAudioName(String audioName) {
MediaPlayer mediaPlayer = new MediaPlayer();
mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
mediaPlayer.setOnCompletionListener(m -> pause(audioName)); // don't call stop, allows reuse
try {
mediaPlayer.setDataSource(FXActivity.getInstance().getAssets().openFd(audioName));
mediaPlayer.setOnPreparedListener(mp -> {
System.out.println("Adding audio resource " + audioName);
playList.put(audioName, mp);
positionList.put(audioName, 0);
});
mediaPlayer.prepareAsync();
} catch (IOException ex) {
System.out.println("Error retrieving audio resource " + audioName + " " + ex);
}
}
#Override
public void play(String audioName, double volume) {
MediaPlayer mp = playList.get(audioName);
if (mp != null) {
if (positionList.get(audioName) > 0) {
positionList.put(audioName, 0);
mp.pause();
mp.seekTo(0);
}
mp.start();
}
}
#Override
public void stop(String audioName) {
MediaPlayer mp = playList.get(audioName);
if (mp != null) {
mp.stop();
}
}
#Override
public void pause(String audioName) {
MediaPlayer mp = playList.get(audioName);
if (mp != null) {
mp.pause();
positionList.put(audioName, mp.getCurrentPosition());
}
}
#Override
public void resume(String audioName) {
MediaPlayer mp = playList.get(audioName);
if (mp != null) {
mp.start();
mp.seekTo(positionList.get(audioName));
}
}
#Override
public void release() {
for (MediaPlayer mp : playList.values()) {
if (mp != null) {
mp.stop();
mp.release();
}
}
}
}
Sample
I've added five short audio files (from here), and added five buttons to my main view:
#Override
public void start(Stage primaryStage) throws Exception {
Button play1 = new Button("p1");
Button play2 = new Button("p2");
Button play3 = new Button("p3");
Button play4 = new Button("p4");
Button play5 = new Button("p5");
HBox hBox = new HBox(10, play1, play2, play3, play4, play5);
hBox.setAlignment(Pos.CENTER);
Services.get(AudioService.class).ifPresent(audio -> {
audio.addAudioName("beep28.mp3");
audio.addAudioName("beep36.mp3");
audio.addAudioName("beep37.mp3");
audio.addAudioName("beep39.mp3");
audio.addAudioName("beep50.mp3");
play1.setOnAction(e -> audio.play("beep28.mp3", 5));
play2.setOnAction(e -> audio.play("beep36.mp3", 5));
play3.setOnAction(e -> audio.play("beep37.mp3", 5));
play4.setOnAction(e -> audio.play("beep39.mp3", 5));
play5.setOnAction(e -> audio.play("beep50.mp3", 5));
});
Scene scene = new Scene(new StackPane(hBox), Screen.getPrimary().getVisualBounds().getWidth(),
Screen.getPrimary().getVisualBounds().getHeight());
primaryStage.setScene(scene);
primaryStage.show();
}
#Override
public void stop() throws Exception {
Services.get(AudioService.class).ifPresent(AudioService::release);
}
The prepare step takes place when the app is launched and the service is instanced, so when playing later on any of the audio files, there won't be any delay.
I haven't checked if there could be any memory issues when adding several media players with big audio files, as that wasn't the initial scenario. Maybe a cache strategy will help in this case (see CacheService in Gluon Charm Down).

changing rate of Animation/Transition

I'm trying to do some basic animations, but am failing at the most simple things:
Rectangle rect = new Rectangle(100.0, 10.0);
mainPane.getChildren().add(rect); //so the rectangle is on screen
Animation anim = new Timeline(new KeyFrame(Duration.seconds(30.0),
new KeyValue(rect.widthProperty(), 0.0, Interpolator.LINEAR)));
rect.setOnMouseClicked(e -> {
if (anim.getStatus() == Status.RUNNING) {
anim.pause();
} else {
anim.setRate(Math.random() * 5.0);
anim.play();
System.out.println(anim.getRate());
}
});
The problem I am facing is that when I click the rectangle multiple times, the size will randomly jump around, instead of just changing the speed at which it drops. So for example, I let it run to about 50% size at speed ~2.5 and then stop it. When I start it up again, it will jump to a totally different size, smaller for a lower speed, bigger for a higher speed, so for example to ~20% for ~1.0 speed or ~80% for ~4.5 speed.
At first I thought animation was pre-calculated for the new speed and thus jumped to the position at which it would be, had it been played with the new speed from the beginning for the time that it was already playing before the pause, but it's bigger for a smaller speed, which doesn't really make sense then.
How do I change the speed/rate of an animation without having it jump around?
I think your diagnosis is correct: the current value is interpolated given the current time and current rate. If you decrease the rate without changing the current time, you are then earlier in the animation. Since the animation is shrinking this has the effect of making the rectangle bigger.
The easiest way is probably just to start a new animation each time:
import javafx.animation.Animation;
import javafx.animation.Animation.Status;
import javafx.animation.Interpolator;
import javafx.animation.KeyFrame;
import javafx.animation.KeyValue;
import javafx.animation.Timeline;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.layout.Pane;
import javafx.scene.shape.Rectangle;
import javafx.stage.Stage;
import javafx.util.Duration;
public class VariableRateAnimation extends Application {
private Animation anim ;
#Override
public void start(Stage primaryStage) {
Pane mainPane = new Pane();
Rectangle rect = new Rectangle(100.0, 10.0);
mainPane.getChildren().add(rect); //so the rectangle is on screen
rect.setOnMouseClicked(e -> {
if (anim != null && anim.getStatus() == Status.RUNNING) {
System.out.println("Paused (" + anim.getTotalDuration().subtract(anim.getCurrentTime())+ " remaining)");
anim.pause();
} else {
Duration duration = Duration.seconds(30.0 * rect.getWidth() / (100 * Math.random() * 5.0));
System.out.println("Starting: ("+duration+ " to go)");
double currentWidth = rect.getWidth() ;
if (anim != null) {
anim.stop();
}
anim = new Timeline(
new KeyFrame(Duration.ZERO, new KeyValue(rect.widthProperty(), currentWidth, Interpolator.LINEAR)),
new KeyFrame(duration, new KeyValue(rect.widthProperty(), 0.0, Interpolator.LINEAR)));
anim.play();
}
});
primaryStage.setScene(new Scene(mainPane, 600, 600));
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}

Slick2D - Same movement on every computer

I am coding a game with friends and we have problems to get the same movement on our different PC. In Slick2D, we use the Delta to make the movement :
if (input key up)
{
sprite = up; // changing the sprite
if (no collision){
sprite.update(delta*3); //animation
y -= delta * 0.3f; // movement
}
}
The problem is that delta is the time between two frames. And this time in not the same on each computer because of performance. So the slower the computer is, the slower the movement will be.
How to solve this problem ?
Thank you for helping us, and I hope you understand my english !
How about setting a target frame rate for your application? It can normalize the speed of movement throughout all computers (given the computer can reach that rate). So whenever you first instantiate your game class, do this:
private static final int TARGET_FRAME_RATE = 60;
private static final int WINDOW_WIDTH = 640;
private static final int WINDOW_HEIGHT = 480;
public static void main(String[] args) {
try {
AppGameContainer appgc = new AppGameContainer(new MyGame());
appgc.setDisplayMode(WINDOW_WIDTH, WINDOW_HEIGHT, false);
// Set the target framerate for the appgc
appgc.setTargetFrameRate(TARGET_FRAME_RATE);
appgc.start();
} catch (SlickException e) {
System.err.println("There was an error executing the game...");
e.printStackTrace();
}
}

Memory usage in IE for gwt application

For the last couple of days I've been trying to find out why my gwt application is leaking on IE 9.
I want to share one of my findings with you and maybe someone can give me a clue about what is going one here...
I wrote this small test:
public class Memory implements EntryPoint
{
FlowPanel mainPanel = new FlowPanel();
FlowPanel buttonsPanel = new FlowPanel();
FlowPanel contentPanel = new FlowPanel();
Timer timer;
Date startDate;
public void onModuleLoad()
{
mainPanel.setWidth("100%");
mainPanel.setHeight("100%");
RootPanel.get().add(mainPanel);
Button startBtn = new Button("start test");
startBtn.addClickHandler(new ClickHandler(){
#Override
public void onClick(ClickEvent event)
{
startDate = new Date();
System.out.println("Started at " + startDate);
timer = new Timer()
{
public void run()
{
Date now = new Date();
if(isWithin5Minutes(startDate, now))
{
manageContent();
}
else
{
System.out.println("Complete at " + new Date());
timer.cancel();
contentPanel.clear();
}
}
};
timer.scheduleRepeating(50);
}
});
buttonsPanel.add(startBtn);
mainPanel.add(buttonsPanel);
mainPanel.add(contentPanel);
}
private void manageContent()
{
if(contentPanel.getWidgetCount() > 0)
{
contentPanel.clear();
}
else
{
for(int i =0; i < 20; i++)
{
Image image = new Image();
image.setUrl("/images/test.png");
contentPanel.add(image);
}
}
}
private boolean isWithin5Minutes(Date start, Date now)
{
//true if 'now' is within 5 minutes of 'start' date
}
}
So, I have this Timer that runs every 50 ms (during around 5 minutes) and executes the following:
- if the panel has content, clear it;
- if the panel has no content add 20 png images (30x30 with transparency) to it.
Using the Process Explorer from sysInternals I got the following results:
IE 9:
Firefox 21.0:
I ran the same program with some changes (.jpg images instead of .png, create the images only once and use them as member variables, create the images using a ClientBundle) but the result was the same. Also, I ran the application in production mode.
Is there something wrong with my code that could cause this behavior in IE?
Shouldn't the Garbage Collector (GC) free some of the used memory at least when the timer ends?
Any of you came across this problem before?
Garbage collector in IE is quite strange thing. E.g. you can force it to run by simply minimizing browser window. I guess leaks in your case are images that weren't removed properly by browser when you clear container. Try to remove them by using JS "delete" operation, like that:
private native void utilizeElement(Element element) /*-{
delete element;
}-*/;
Then change your manageContent a little:
if(contentPanel.getWidgetCount() > 0)
{
for (Iterator<Widget> it = contentPanel.iterator(); it.hasNext();)
utilizeElement(it.next().getElement());
contentPanel.clear();
}
Hope this helps.

keyboard vs menu conflict on Java 7, Mac OS

How can I get a Java 7 application to have its menu bar at the top of the screen (on a Mac) and also have correctly working keyboard shortcuts?
I have a Java application with a Swing user interface. Many menus have keyboard equivalents, which are essential.
There is very little that is system-dependent, but on Mac OS X the menu bar should appear at the top of the screen instead of on each window, so I set apple.laf.useScreenMenuBar.
This works fine on Java 6, but on Java 7 (out last week!) compiling and running the same code causes the keyboard shortcuts to carry out their menu actions twice. For example, in the attached code, Command ⌘ + O opens two file dialogues instead of one. (The other keyboard shortcuts also act twice, but you sometimes have to move windows to see that they did.)
The keyboard problem goes away if I don't set apple.laf.useScreenMenuBar, and that's what I'll do if I have to, but my Mac users will be displeased. I'd really like to have the menu bar in the right place and the keyboard shortcuts working.
System: Mac OS 10.7.3 (Lion) on a late-2010 MacBook Pro
Java 7:
java version "1.7.0_04"
Java(TM) SE Runtime Environment (build 1.7.0_04-b21)
Java HotSpot(TM) 64-Bit Server VM (build 23.0-b21, mixed mode)
Java 6:
java version "1.6.0_31"
Java(TM) SE Runtime Environment (build 1.6.0_31-b04-415-11M3635)
Java HotSpot(TM) 64-Bit Server VM (build 20.6-b01-415, mixed mode)
Where I've looked:
A discussion of why apple.laf.useScreenMenuBar should be gotten rid of
-- I'm all for it, but it doesn't seem to have happened.
A discussion about not using mrj.version to detect that you're on a Mac
-- not directly relevant, but sounded promising.
My apologies for the length of the attached code (148 lines), but my Swing coding is very old-fashioned. It should compile and run from the command line without any special flags or settings.
import javax.swing.*;
import java.awt.Toolkit;
import java.awt.*;
import java.awt.event.*;
/**
* Shows that using the single screen-top menu bar on a Mac with Java 7
* causes keyboard shortcuts to act twice.
*
* To see the problem(on a Mac -- running OS X 10.7.3 in my case):
* 1) compile on either Java 6 or Java 7
* 2) run on Java 7
* 3) give the command-O shortcut
* You will see two file dialogues.
*
* -- J. Clarke, May 2012
*/
public class MenuBug {
private static void go(String[] args) {
// Comment out the following line to fix the problem;
// leave it active to see the problem.
// It doesn't help to ...
// ... put the line into a static block.
// ... put the line right after the setLookAndFeel call.
// ... put the line before after the setLookAndFeel call.
System.setProperty("apple.laf.useScreenMenuBar", "true");
MainWindow mainWindow = new MainWindow();
}
public static void main(final String[] args) {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
}
catch (Exception e) {
JOptionPane.showMessageDialog(null,
e + " while loading look and feel",
"MenuBug error", JOptionPane.ERROR_MESSAGE);
System.exit(1);
}
SwingUtilities.invokeLater(new Runnable() {
public void run() {
go(args);
}
});
}
}
class MainWindow extends JFrame {
MainWindow() {
super ("Main Window");
setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE);
addWindowListener (new WindowAdapter() {
public void windowClosing(WindowEvent e) {
dispose();
System.exit(0);
}
});
JMenuBar menuBar = createMenuBar();
setJMenuBar(menuBar);
pack();
setSize(350,300);
setVisible(true);
}
private JMenuBar createMenuBar() {
JMenuBar mBar = new JMenuBar();
JMenu menu = new JMenu("File");
String[] menuItemNames = new String[] {"New", "Open...", "Other"};
for (int i = 0; i < menuItemNames.length; i++) {
String miName = menuItemNames[i];
JMenuItem mi = new JMenuItem(miName);
mi.setActionCommand(miName);
linkMenuItemToAction(mi);
menu.add(mi);
}
mBar.add(menu);
return mBar;
}
/**
* Create an Action for menuItem, and make sure the action and the menu
* item know about each other; where appropriate, add keyboard equivalents.
* #param menuItem The menu item to be linked to an action.
*/
private void linkMenuItemToAction(JMenuItem menuItem) {
final int META_MASK =
Toolkit.getDefaultToolkit().getMenuShortcutKeyMask();
Action a = null;
String miName = menuItem.getActionCommand();
if (miName.equals ("New")) {
a = new NewAction();
menuItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_N,
META_MASK));
}
else if (miName.equals ("Open...")) {
a = new OpenAction();
menuItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_O,
META_MASK));
}
else if (miName.equals ("Other")) {
a = new OtherAction();
menuItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_T,
META_MASK));
}
menuItem.setEnabled(a.isEnabled());
menuItem.addActionListener(a);
}
private class NewAction extends AbstractAction {
public void actionPerformed(ActionEvent e) {
new MainWindow();
}
}
private void makeDialog() {
String dialogTitle = "Please choose a file to open";
FileDialog fileDialog = new FileDialog(this, dialogTitle,
FileDialog.LOAD);
fileDialog.setVisible(true);
String fileName = fileDialog.getFile();
}
private class OpenAction extends AbstractAction {
public void actionPerformed(ActionEvent e) {
makeDialog();
}
}
private class OtherAction extends AbstractAction {
public void actionPerformed(ActionEvent e) {
JOptionPane.showMessageDialog(null,
"an example message",
"not really an error", JOptionPane.ERROR_MESSAGE);
}
}
}
I'm answering my own question -- sort of. As noted in the comments to the original, the trouble goes away with Java 1.7u10.
It looks like that this problem still exist but now it can be reproduced with fn + backSpace (delete) on mac with 1.7_21.
I used the same example as above just added text field. Select part of text in textfield and press delete (fn+backspace)
Change KeyStroke to "DELETE" in linkMenuItemToAction method
else if (miName.equals ("Other"))
{
a = new OtherAction();
menuItem.setAccelerator(KeyStroke.getKeyStroke("DELETE"));
}
and add this:
JTextField textField = new JTextField(10);
textField.setText("Long long long long long long long text");
add(textField, BorderLayout.PAGE_START);
to MainWindow constructor.

Resources