Bad display for JavaFX WebView under macOS - java

We are using JavaFX WebView as our in-app browser. It works well under Windows 10.
However, when we run in under macOS Catalina 10.15.7, all display gone hair wire. The app is run on Java 8.
Here's the code for our simple in-app browser.
SimpleSwingBrowser.java
public class SimpleSwingBrowser extends JDialog {
private final JFXPanel jfxPanel = new JFXPanel();
private WebEngine engine;
private String loadedURL = null;
private final JPanel panel = new JPanel(new BorderLayout());
public SimpleSwingBrowser() {
super(JStock.instance(), JDialog.ModalityType.APPLICATION_MODAL);
initComponents();
}
private void initComponents() {
createScene();
// http://stackoverflow.com/questions/11269632/javafx-hmtleditor-doesnt-react-on-return-key
jfxPanel.addKeyListener(new KeyAdapter() {
#Override
public void keyTyped(KeyEvent e) {
if (e.getKeyChar() == 10) {
e.setKeyChar((char) 13);
Toolkit.getDefaultToolkit().getSystemEventQueue().postEvent(e);
}
}
});
panel.add(jfxPanel, BorderLayout.CENTER);
getContentPane().add(panel);
java.awt.Dimension screenSize = java.awt.Toolkit.getDefaultToolkit().getScreenSize();
setBounds((screenSize.width-460)/2, (screenSize.height-680)/2, 460, 680);
}
private void createScene() {
Platform.runLater(new Runnable() {
#Override
public void run() {
final WebView view = new WebView();
engine = view.getEngine();
engine.titleProperty().addListener(new ChangeListener<String>() {
#Override
public void changed(ObservableValue<? extends String> observable, String oldValue, final String newValue) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
SimpleSwingBrowser.this.setTitle(newValue);
}
});
}
});
engine.getLoadWorker().stateProperty().addListener(new ChangeListener<State>() {
#Override
public void changed(ObservableValue<? extends State> observable, State oldValue, final State newValue) {
if (newValue == FAILED) {
final int result = JOptionPane.showConfirmDialog(
panel,
MessagesBundle.getString("error_message_unable_connect_to_internet"),
MessagesBundle.getString("error_title_unable_connect_to_internet"),
JOptionPane.YES_NO_OPTION);
if (result == JOptionPane.YES_OPTION) {
if (loadedURL != null) {
engine.load(loadedURL);
}
}
}
}
});
// http://stackoverflow.com/questions/11206942/how-to-hide-scrollbars-in-the-javafx-webview
// hide webview scrollbars whenever they appear.
view.getChildrenUnmodifiable().addListener(new ListChangeListener<Node>() {
#Override
public void onChanged(Change<? extends Node> change) {
Set<Node> deadSeaScrolls = view.lookupAll(".scroll-bar");
for (Node scroll : deadSeaScrolls) {
scroll.setVisible(false);
}
}
});
jfxPanel.setScene(new Scene(view));
}
});
}
public void loadURL(final String url) {
Platform.runLater(new Runnable() {
#Override
public void run() {
String tmp = toURL(url);
if (tmp == null) {
tmp = toURL("http://" + url);
}
loadedURL = tmp;
engine.load(tmp);
}
});
}
private static String toURL(String str) {
try {
return new URL(str).toExternalForm();
} catch (MalformedURLException exception) {
return null;
}
}
}
Any idea why this happens? Is there anything I can do to resolve? Thanks.

As also stated in this answer you can try to use another user agent. I used the following user agent (which is simulating Firefox on macOS) some time ago when I had a similar issue with Java 1.8.0_66:
webView.getEngine().setUserAgent("Mozilla/5.0 (Macintosh; Intel Mac OS X 10.10; rv:54.0) Gecko/20100101 Firefox/54.0");
That did help for some sites but not for all (e.g. Google Maps was using correct font with above user agent but not loading completly due to other issues). Updating to Java 9 (9.0.1_11 at that time) gave better results. Later I tried the following approach:
String userAgent = this.browser.getEngine().getUserAgent();
if (userAgent != null) {
String[] parts = userAgent.split(" ");
String manipulatetUserAgent = "";
for (int i = 0; i < parts.length; i++) {
if (i > 0) {
manipulatetUserAgent += " ";
}
if (parts[i] != null && parts[i].contains("JavaFX")) {
manipulatetUserAgent += "Version/13.1.1"; // current safari version
} else {
manipulatetUserAgent += parts[i];
}
}
this.browser.getEngine().setUserAgent(manipulatetUserAgent);
}
But actually some month ago I went from trying to fix JavaFX WebView to learning how to use Java Chromium Embedded Framework (JCEF) which is
a simple framework for embedding Chromium-based browsers in other applications using the Java programming language.
It certainly has more overhead during integration but it looks promising.
I was really enthusiatic about WebView at the beginning but encountered to many bugs over time. First I waited for the bugs to be fixed, but then more bugs appeared. Last thing that was frustrating me was login not working reliably on some sites (probably because they were using Google's reCAPTCHA). So be aware that the user agent fix may only help temporary if it's working at all.

Related

Worldwind SurfaceImage Deep/Batch Picking

I'm using WorldWind and trying to "pick" multiple surface images in the same layer and not understanding why it isn't working.
I was under the impression that calling this:
this.getWwd().getSceneController().setDeepPickEnabled(true);
Would enable me to pick multiple renderables in the same layer. This seems to work for all other cases other than SurfaceImage. I also noticed if I force the loaded SurfaceImage into different layers it works as expected.
This is the code I'm using to test this out:
public class SurfaceImageViewer extends ApplicationTemplate
{
public static class AppFrame extends ApplicationTemplate.AppFrame
{
private JFileChooser fileChooser = new JFileChooser();
private JSlider opacitySlider;
private SurfaceImageLayer layer;
private JLabel statusLabel = new JLabel("status: ready");
public AppFrame()
{
super(true, true, false);
this.getWwd().getSceneController().setDeepPickEnabled(true);
try
{
this.layer = new SurfaceImageLayer();
this.layer.setOpacity(1);
this.layer.setPickEnabled(true);
this.layer.setName("Surface Images");
insertBeforeCompass(this.getWwd(), layer);
this.getControlPanel().add(makeControlPanel(), BorderLayout.SOUTH);
}
catch (Exception e)
{
e.printStackTrace();
}
this.getWwd().addSelectListener(new SelectListener() {
#Override
public void selected(SelectEvent event) {
PickedObjectList pol = AppFrame.this.getWwd().getObjectsAtCurrentPosition();
if(event.isLeftClick()){
System.out.println("POL SIZE "+pol.size());
}
}
});
}
Action openElevationsAction = new AbstractAction("Open Elevation File...")
{
public void actionPerformed(ActionEvent e)
{
int status = fileChooser.showOpenDialog(AppFrame.this);
if (status != JFileChooser.APPROVE_OPTION)
return;
final File imageFile = fileChooser.getSelectedFile();
if (imageFile == null)
return;
Thread t = new Thread(new Runnable()
{
public void run()
{
try
{
CompoundElevationModel cem
= (CompoundElevationModel) getWwd().getModel().getGlobe().getElevationModel();
LocalElevationModel em = new LocalElevationModel();
em.addElevations(imageFile.getPath());
cem.addElevationModel(em);
}
catch (IOException e1)
{
e1.printStackTrace();
}
}
});
t.setPriority(Thread.MIN_PRIORITY);
t.start();
}
};
Action openImageAction = new AbstractAction("Open Image File...")
{
public void actionPerformed(ActionEvent actionEvent)
{
int status = fileChooser.showOpenDialog(AppFrame.this);
if (status != JFileChooser.APPROVE_OPTION)
return;
final File imageFile = fileChooser.getSelectedFile();
if (imageFile == null)
return;
Thread t = new Thread(new Runnable()
{
public void run()
{
try
{
statusLabel.setText("status: Loading image");
// TODO: proper threading
layer.addImage(imageFile.getAbsolutePath());
getWwd().redraw();
statusLabel.setText("status: ready");
}
catch (IOException e)
{
e.printStackTrace();
}
}
});
t.setPriority(Thread.MIN_PRIORITY);
t.start();
}
};
private JPanel makeControlPanel()
{
JPanel controlPanel = new JPanel(new GridLayout(0, 1, 5, 5));
JButton openImageButton = new JButton(openImageAction);
controlPanel.add(openImageButton);
this.opacitySlider = new JSlider();
this.opacitySlider.setMaximum(100);
this.opacitySlider.setValue((int) (layer.getOpacity() * 100));
this.opacitySlider.setEnabled(true);
this.opacitySlider.addChangeListener(new ChangeListener()
{
public void stateChanged(ChangeEvent e)
{
int value = opacitySlider.getValue();
layer.setOpacity(value / 100d);
getWwd().redraw();
}
});
JPanel opacityPanel = new JPanel(new BorderLayout(5, 5));
opacityPanel.setBorder(new EmptyBorder(0, 10, 0, 0));
opacityPanel.add(new JLabel("Opacity"), BorderLayout.WEST);
opacityPanel.add(this.opacitySlider, BorderLayout.CENTER);
controlPanel.add(opacityPanel);
JButton openElevationsButton = new JButton(openElevationsAction);
controlPanel.add(openElevationsButton);
controlPanel.add(statusLabel);
controlPanel.setBorder(new EmptyBorder(15, 15, 15, 15));
return controlPanel;
}
}
public static void main(String[] args)
{
ApplicationTemplate.start("World Wind Surface Images", SurfaceImageViewer.AppFrame.class);
}
}
These are 2 geotiffs that are layered on top of each other that I've been using to test this out. I would expect my println on the SelectListener to print out "3" when I single left click on both geotiffs. (I've uploaded the geotiffs into a zip available here)
The area where you will see these is in San Francisco, see screenshot:
Update:
It was discovered that the examples for Batch Picking were oriented around AbstractSurfaceObject instances, which did not apply in this case. For the handling of SurfaceImage instances the property for setAlwaysOnTop should be configured to false which appears to let the selection event process all elements under the cursor.
Reading through the examples for DeepPicking, there are actually 2 things that need to be done.
setDeepPickEnabled(true); //This is done.
Disable Batch picking on the desired elements
https://github.com/nasa/World-Wind-Java/blob/master/WorldWind/src/gov/nasa/worldwindx/examples/DeepPicking.java
In order to enable deep picking, any batch picking for the desired elements must be disabled and the
SceneController's deep picking property must be enabled. See {#link gov.nasa.worldwind.SceneController#setDeepPickEnabled(boolean)
Took me a little while to understand the second one, but it appears to be tied to the AbstractSurfaceObject class.
I am assuming that the things that you're drawing on the layer are a subclass of AbstractSurfaceObject
I believe that in this situation, I would subclass the SurfaceImageLayer, and override the addRenderable methods. I would check the renderable if it was an instance of an AbstractSurfaceObject, and disable batch picking on it before forwarding it to the super class.
This code may not be the best long-term solution, but it may provide quick results to determine if this is the underlying issue.
import gov.nasa.worldwind.layers.SurfaceImageLayer;
import gov.nasa.worldwind.render.AbstractSurfaceObject;
import gov.nasa.worldwind.render.Renderable;
/**
* Very Rough extension of SurfaceImageLayer which disables batch picking on all AbstractSurfaceobjects.
* #author http://stackoverflow.com/users/5407189/jeremiah
* #since Nov 26, 2016
*
*/
public class MySurfaceImageLayer extends SurfaceImageLayer {
#Override
public void addRenderable(Renderable renderable) {
if (renderable instanceof AbstractSurfaceObject) {
((AbstractSurfaceObject)renderable).setEnableBatchPicking(false);
}
super.addRenderable(renderable);
}
#Override
public void addRenderables(Iterable<? extends Renderable> renderables) {
for (Renderable r : renderables) {
addRenderable(r);
}
}
}
IF the thing you want to have picked is the image directly, that appears to not be supported out-of-the-box. You would need to do something to get the SurfaceImage references from the SurfaceImageLayer to be visible to the RenderableLayer on doPick. That may come with a new set of problems to watch out for.
As a side-note, if you're rendering Icons then all you need to do is set the IconRenderer.setAllowBatchPicking(false)
I hope that's at least somewhat helpful.
Best of Luck.

JDialog invisible, components clickable

I'm using a JDialog instatiated at startup of the application, to show messages several times. Sometimes the dialog and its controls are invisible, but clickable.
The JDialog is instantiated only once and set to visible 'true' each time a message should be shown and then set to visible 'false' till the next message should be shown.
To exlude multithreading related problems, i always use SwingUtilities.invokeLater(...) for ui calls, when a thread creates a message and shows the dialog.
Because its a huge project and my problem isn't related to any specific code, i don't post code but describe the problem. The problems seems not to be reproducible but happens sometimes, so it might be a threading problem despite running each related call on the EDT.
What am I doing wrong?
public class MessageHandler {
private volatile static MessageHandler messageHandler = null;
private List<Message>messages = null;
private volatile WeakReference<MessagesPanelControl> view = null;
private final Object viewSynchronizationObject = new Object();
private MessageHandler() {
messages = new ArrayList<Message>();
}
public static MessageHandler getInstance() {
MessageHandler result = messageHandler;
if (result == null) {
synchronized (MessageHandler.class) {
result = messageHandler;
if (result == null)
messageHandler = result = new MessageHandler();
}
}
return result;
}
public void registerView(MessagesPanelControl view) {
this.view = new WeakReference<MessagesPanelControl>(view);
}
public void addMessage(final Message message) {
synchronized (viewSynchronizationObject) {
messages.add(message);
Collections.sort(messages);
updateView();
}
}
private void updateView() {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
synchronized (viewSynchronizationObject) {
if (view == null) {
return;
}
MessagesPanelControl mpc = view.get();
if (mpc != null) {
mpc.updateView();
}
}
}
});
}
}
In the MainFrame class i'm doing this initialization once at startup:
MessagesPanelControl mp = new MessagesPanelControl();
MessageHandler.getInstance().registerView(mp);
LockPane messageBasicPane = new LockPane(this, mp);
And then in different threads this is called to show a message via the MessageHandler Singleton:
MessageHandler.getInstance().addMessage(Message.getSimpleMessage("Error", "Fatal error occured", Message.MessageIcon.ERROR));
I didn't post all details, but all necessary parts to understand the whole problme, hope it makes it more understandable.
The MessagePanelControl (mpc) is a class, that extends JPanel. Its updateView()-method creates the message controlls based on the MessageHandler's message list like buttons, labels and icons. Finally the method sends a Delegate like command to the main frame to show the JDialog containing the MessagePanelControl.
Summarized it does:
messageList.size()>0: create message panels for each message in list in MessageHandler
messageList.size()>0: show JDialog with MessagePanelControl
messageList.size()<=0: hide JDialog with MessagePanelControl
public void updateView() {
synchronized (viewMPCSynchronizationObject) {
Utils.throwExceptionWhenNotOnEDT();
JPanel messagesListPanel = new JPanel();
scrollPane.setViewportView(messagesListPanel);
scrollPane.setBorder(null);
messagesListPanel.setLayout(new BoxLayout(messagesListPanel, BoxLayout.Y_AXIS));
if (MessageHandler.getInstance().getMessages() != null && MessageHandler.getInstance().getMessages().size() > 0) {
[...]
//Create buttons, text icons... for each message
[...]
SwingUtilities.invokeLater(new Runnable() {
public void run() {
MainFrame().showMessageBoard();
}
});
} else {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
MainFrame().closeMessageBoard();
}
});
}
repaint();
}
}
MainFrame:
//Show Messageboard
public void showMessageBoard() {
if (messageBasicPane != null) {
messageBasicPane.setVisible(true);
messageBasicPane.repaint();
}
}
[...]
//Close Messageboard
public void closeMessageBoard() {
if (messageBasicPane != null) {
messageBasicPane.setVisible(false);
}
}
This line creates the JDialog, in detail:
[...]
public LockPane(JFrame parentFrame, JComponent componentToShow, Dimension paneSize, float opacity, ModalityType modality) {
super(parentFrame, true);
Utils.throwExceptionWhenNotOnEDT();
createDialog(paneSize, opacity, modality);
if (componentToShow != null) {
add(componentToShow);
}
pack();
}
private void createDialog(Dimension paneSize, float opacity, ModalityType modality) {
Utils.throwExceptionWhenNotOnEDT();
setUndecorated(true);
setModalityType(modality);
if (opacity < 1 && opacity >= 0)
com.sun.awt.AWTUtilities.setWindowOpacity(this, opacity);
setSize(paneSize);
setPreferredSize(paneSize);
setMaximumSize(paneSize);
setBounds(0, 0, paneSize.width, paneSize.height);
setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);
}
[...]
A new observation with Java VisualVM is, that the AWT-EventQueue isn't blocked, only sometime there are small periods of 'wait' but nothing blocking. Another strange thing is, that sometimes my JDialog is fully transparent (invisible) and sometimes its white with the desired opacity.
In this function, you are essentially trying to await the Runnable passed to the SwingUtilities.invokeLater which invokeLater submits to the EDT to get executed. The lock that you are holding on viewSynchronizationObject will block EDT if it is locked by other application thread which is actually evident from your code as you have used this variable in several other places.
private void updateView() {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
synchronized (viewSynchronizationObject) {
if (view == null) {
return;
}
MessagesPanelControl mpc = view.get();
if (mpc != null) {
mpc.updateView();
}
}
}
},false);
}
We should never block EDT from executing it's task other wise our application freezes. Please read my posted answer here for details on how the Swing event and rendering task is performed by EDT and EventQueue.
Although, your application logic is not known to us, you can remove the synchronized (viewSynchronizationObject) {} from invokeLater, instead you can put SwingUtilities.invokeLater(new Runnable() {} inside this synchronized block:
synchronized (viewSynchronizationObject)
{
SwingUtilities.invokeLater(new Runnable() { /* your code */ } );
}

Java AWT multiple screens don't show up

I'm working on a task-planning AWT applet for my dev team, and I'm running into a problem.
I'm using a screen system, where the main class has a "current screen" variable that it uses to paint other screens. When the applet starts, it loads the "main screen" which has a "Chatroom" button. When you click the button, it should open the chatroom screen.
My problem is that it displays the main screen just fine, but when you click the button everything just goes blank and the chatroom does not show up at all. What am I doing wrong?
Each screen is a subclass of the Screen class, which is a subclass of Container.
Main Class:
public class TPApplet extends Applet
{
private static final long serialVersionUID = 7611084043153150559L;
private static final int WIDTH = 400;
private static final int HEIGHT = 350;
private static final String TITLE = "TaskPlanner v";
private static final double VERSION = 0.01;
private boolean setup = false;
public Screen currentScreen;
public void init()
{
setLayout(null);
setScreen(new MainScreen(this));
}
public void stop()
{
}
public void setScreen(Screen s)
{
if (currentScreen != null)
{
currentScreen.destroy();
remove(currentScreen);
}
currentScreen = s;
if (currentScreen != null)
{
currentScreen.init();
add(currentScreen);
}
}
public void paint(Graphics g)
{
if (!setup)
{
setSize(WIDTH, HEIGHT);
setName(TITLE + VERSION);
currentScreen.setLocation(0, 0);
currentScreen.setSize(WIDTH, HEIGHT);
setup = true;
}
if (currentScreen != null)
{
currentScreen.paint(g);
}
}
}
Main Menu class:
public class MainScreen extends Screen
{
private static final long serialVersionUID = -993648854350389881L;
private TPApplet applet;
private Button todoButton;
private Button chatButton;
private boolean setup = false;
public MainScreen(TPApplet tpApplet)
{
applet = tpApplet;
}
#Override
public void init()
{
setLayout(null);
todoButton = createButton("To-Do List");
chatButton = createButton("Chatroom");
}
#Override
public void destroy()
{
removeAll();
}
#Override
public void paint(Graphics g)
{
if (!setup)
{
todoButton.setLocation(25, 50);
todoButton.setSize(100, 40);
chatButton.setLocation(135, 50);
chatButton.setSize(100, 40);
setup = true;
}
}
#Override
public void actionPerformed(ActionEvent e)
{
if (e.getSource() instanceof Button)
{
Button button = (Button) e.getSource();
if (button.getLabel() == chatButton.getLabel())
{
applet.setScreen(new ChatScreen(applet));
}
}
}
}
Chatroom Class:
public class ChatScreen extends Screen
{
private static final long serialVersionUID = -8774060448361093669L;
private TPApplet applet;
private ScrollPane chatWindow;
private TextField textField;
private Button sendButton;
private boolean setup = false;
public ChatScreen(TPApplet tpApplet)
{
applet = tpApplet;
}
#Override
public void init()
{
setLayout(null);
sendButton = createButton("Send");
chatWindow = new ScrollPane();
textField = new TextField();
add(chatWindow);
add(textField);
}
#Override
public void destroy()
{
removeAll();
}
#Override
public void paint(Graphics g)
{
if (!setup)
{
chatWindow.setLocation(20, 20);
chatWindow.setSize(100, 100);
textField.setLocation(150, 150);
textField.setSize(60, 20);
sendButton.setLocation(220, 150);
sendButton.setSize(40, 20);
setup = true;
}
}
#Override
public void actionPerformed(ActionEvent e)
{
if (e.getSource() instanceof Button)
{
Button button = (Button) e.getSource();
if (button.getLabel() == sendButton.getLabel())
{
String text = textField.getText();
}
}
}
}
Thank you in advance for your help!
I suspect that seen as you've chosen to discard the use of layout managers, when you add a new screen, the screen is being added with a 0x0 size
public void setScreen(Screen s)
{
//...//
if (currentScreen != null)
{
currentScreen.init();
// Look ma, I have no size...
add(currentScreen);
}
}
One of the jobs of a layout manger is to decide how any new components should be laid out.
Try setting the applet's layout manager to something like BorderLayout.
The next problem is that the child screens suffer from the same problem, so even though the screen will be sized (based on the needs of the layout manager), the screens themselves also have no layout manager, so the components you add to them have no size and it will appear that the screen hasn't been updated.
I'd also recommend that you take a look at Andrew's example of CardLayout
You could also check out A Visual Guide to Layout Managers and Using Layout Managers for more details...
You will need to invalidate() the applet in your setScreen method.
The new screen component needs to be laid out again to compute the sizes of its children.
It's a shame this isn't done automatically when adding!
Also, consider doing this using a LayoutManager if possible. Would a CardLayout work for you?

Eclipse RCP application - custom splash screen

I'm currently developing an Eclipse RCP application, in which I'm trying to implement a custom splash screen handler, sporting a progress bar (behavior similar to the default progress bar you can define in the .product definition) and multiple cycling background images.
After editing the extensions of the main application plugin this way:
[...]
<!-- install custom splash handler -->
<extension point="org.eclipse.ui.splashHandlers">
<splashHandler
class="com.example.application.splash.SlideShowSplashHandler"
id="splash.slideshow">
</splashHandler>
<splashHandlerProductBinding
productId="com.example.application.product"
splashId="com.example.application.splash.slideshow">
</splashHandlerProductBinding>
</extension>
<!-- define images (in plugin root directory) to be shown -->
<extension point="com.example.application.splashExtension">
<splashExtension id="01" image="01_Splash2Ag.bmp"></splashExtension>
<splashExtension id="02" image="02_Splash3Ag.bmp"></splashExtension>
<splashExtension id="00" image="00_Splash1Ag.bmp"></splashExtension>
</extension>
[...]
I'm trying to implement the custom splashscreen handler class:
public class SlideShowSplashHandler extends AbstractSplashHandler {
private List<Image> fImageList;
private ProgressBar fBar;
private final static String F_SPLASH_EXTENSION_ID = "com.example.application.splashExtension"; //NON-NLS-1
private final static String F_ELEMENT_IMAGE = "image"; //NON-NLS-1
private int imageIdx = 0;
public SlideShowSplashHandler() {
fImageList = new ArrayList<Image>(5);
}
/* (non-Javadoc)
* #see org.eclipse.ui.splash.AbstractSplashHandler#init(org.eclipse.swt.widgets.Shell)
*/
public void init(Shell splash) {
// Store the shell
super.init(splash);
// Force shell to inherit the splash background
getSplash().setBackgroundMode(SWT.INHERIT_DEFAULT);
// Load all splash extensions
loadSplashExtensions();
// If no splash extensions were loaded abort the splash handler
if (hasSplashExtensions() == false) return;
// Create UI
createUI(splash);
}
private boolean hasSplashExtensions() {
if (fImageList.isEmpty()) {
return false;
} else {
return true;
}
}
#Override
public IProgressMonitor getBundleProgressMonitor() {
return new NullProgressMonitor() {
#Override
public void beginTask(String name, final int totalWork) {
getSplash().getDisplay().syncExec(new Runnable() {
public void run() {
fBar.setSelection(50);
}
});
}
#Override
public void subTask(String name) {
getSplash().getDisplay().syncExec(new Runnable() {
public void run() {
if (fBar.getSelection() < 100) fBar.setSelection(fBar.getSelection() + 10);
if (imageIdx >= fImageList.size()) imageIdx = 0;
Image image = fImageList.get(imageIdx++);
getSplash().setBackgroundImage(image);
getSplash().setRedraw(true);
getSplash().redraw();
}
});
}
};
}
private void createUI(Shell shell) {
Composite container = new Composite(shell, SWT.NONE);
container.setLayout(new GridLayout(1, false));
container.setLocation(5, 374);
container.setSize(480, 15);
/* Progress Bar */
fBar = new ProgressBar(container, SWT.HORIZONTAL);
fBar.setLayoutData(new GridData(SWT.FILL, SWT.BEGINNING, true, false));
((GridData) fBar.getLayoutData()).heightHint = 13;
fBar.setMaximum(100);
fBar.setSelection(25);
/* Version Label */
Label versionLabel = new Label(container, SWT.NONE);
versionLabel.setLayoutData(new GridData(SWT.END, SWT.BEGINNING, true, false));
//versionLabel.setFont(fVersionFont);
//versionLabel.setForeground(fVersionColor);
//versionLabel.setText(NLS.bind(Messages.SplashHandler_BUILD, "2.1 Nightly")); //$NON-NLS-1$
/* Layout All */
shell.layout(true, true);
}
private void loadSplashExtensions() {
// Get all splash handler extensions
IExtension[] extensions = Platform.getExtensionRegistry()
.getExtensionPoint(F_SPLASH_EXTENSION_ID).getExtensions();
// Process all splash handler extensions
for (int i = 0; i < extensions.length; i++) {
processSplashExtension(extensions[i]);
}
}
/**
* Parse the extension points with the images filename.
*/
private void processSplashExtension(IExtension extension) {
// Get all splash handler configuration elements
IConfigurationElement[] elements = extension.getConfigurationElements();
// Process all splash handler configuration elements
for (int j = 0; j < elements.length; j++) {
processSplashElements(elements[j]);
}
}
/**
* Create the images defined as extension points
*/
private void processSplashElements(IConfigurationElement configurationElement) {
String name = configurationElement.getAttribute(F_ELEMENT_IMAGE);
ImageDescriptor descriptor = Activator.getImageDescriptor("/"+name);
if (descriptor != null) {
Image image = descriptor.createImage();
if (image !=null) {
fImageList.add(image);
}
}
}
public void dispose() {
super.dispose();
// Check to see if any images were defined
if ((fImageList == null) ||
fImageList.isEmpty()) {
return;
}
// Dispose of all the images
Iterator<Image> iterator = fImageList.iterator();
while (iterator.hasNext()) {
Image image = iterator.next();
image.dispose();
}
}
}
Problem is that the progress bar just works, while the images are not shown. While debugging I could verify that the images are actually found and loaded, and correctly set in the shell; the shell just seems to not being redrawn. Am i missing something?=
I could solve the problem on linux and windows, but it did not work on macos/cocoa (in which the splash screen is looking "scrambled" on each image slideshow iteration).
Is was very simple indeed, just attaching an extra Composite between the splash shell and the container containing the widgets; then change the background image on the newly create container object.
private void createUI(Shell shell) {
Composite bgcontainer = new Composite(shell, SWT.NONE); // new
[...]
Composite container = new Composite(bgcontainer, SWT.NONE);
[...]
fBar = new ProgressBar(container, SWT.HORIZONTAL);
[...]
Label versionLabel = new Label(container, SWT.NONE);
versionLabel.setLayoutData(new GridData(SWT.END, SWT.BEGINNING, true, false));
shell.layout(true, true);
}
#Override public IProgressMonitor getBundleProgressMonitor() {
return new NullProgressMonitor() {
#Override public void beginTask(String name, final int totalWork) {
getSplash().getDisplay().syncExec(new Runnable() {
public void run() {
if (fBar != null) fBar.setSelection(40);
Image image = fImageList.get(imageIdx++);
bgcontainer.setBackgroundImage(image);
bgcontainer.setRedraw(true);
bgcontainer.update();
}
});
}
#Override public void subTask(String name) {
final String n = name;
getSplash().getDisplay().syncExec(new Runnable() {
String taskname = n;
public void run() {
if (fBar != null && fBar.getSelection() < 100)
fBar.setSelection(fBar.getSelection() + 10);
if (fBar.getSelection() == 60 || fBar.getSelection() == 80) {
if (imageIdx >= fImageList.size()) imageIdx = 0;
Image image = fImageList.get(imageIdx++);
bgcontainer.setBackgroundImage(image);
bgcontainer.setRedraw(true);
bgcontainer.update();
}
}
});
}
};
}
I haven't tried your code, but when you make changes to a Control, it is not enough to call Control.redraw(), but you must also call Control.update().
Control.redraw() requests that a control should be redrawn, Control.update() actually redraws it. The later is needed when your code runs on the UI thread!

Getting input from barcode scanner internally without textbox

I have a barcode scanner and in my java application I have to bring a popup to display all the information associated with the barcode from database when the product is scanned using barcode. I have no textbox on the application I have to handle this part internally. How do I do this ? any suggestion ? I am using swing for UI.
EDIT
Barcode scanner is USB one. If we scan something it will output the result into the textbox which has focus. But I have no textbox working on the page opened. Can i work with some hidden textbox and read the value there ?
Since barcode scanner is just a device which sends keycodes and ENTER after reading of each barcode, I'd use a key listener.
final Frame frame = new Frame();
frame.setVisible(true);
frame.addKeyListener(new KeyAdapter() {
#Override
public void keyReleased(KeyEvent e) {
if(e.getKeyCode() == KeyEvent.VK_ENTER) {
// your code is scanned and you can access it using frame.getBarCode()
// now clean the bar code so the next one can be read
frame.setBarCode(new String());
} else {
// some character has been read, append it to your "barcode cache"
frame.setBarCode(frame.getBarCode() + e.getKeyChar());
}
}
});
Since was not able to get input via frame.addKeyListener I have used this utility class which uses KeyboardFocusManager :
public class BarcodeReader {
private static final long THRESHOLD = 100;
private static final int MIN_BARCODE_LENGTH = 8;
public interface BarcodeListener {
void onBarcodeRead(String barcode);
}
private final StringBuffer barcode = new StringBuffer();
private final List<BarcodeListener> listeners = new CopyOnWriteArrayList<BarcodeListener>();
private long lastEventTimeStamp = 0L;
public BarcodeReader() {
KeyboardFocusManager.getCurrentKeyboardFocusManager().addKeyEventDispatcher(new KeyEventDispatcher() {
#Override
public boolean dispatchKeyEvent(KeyEvent e) {
if (e.getID() != KeyEvent.KEY_RELEASED) {
return false;
}
if (e.getWhen() - lastEventTimeStamp > THRESHOLD) {
barcode.delete(0, barcode.length());
}
lastEventTimeStamp = e.getWhen();
if (e.getKeyCode() == KeyEvent.VK_ENTER) {
if (barcode.length() >= MIN_BARCODE_LENGTH) {
fireBarcode(barcode.toString());
}
barcode.delete(0, barcode.length());
} else {
barcode.append(e.getKeyChar());
}
return false;
}
});
}
protected void fireBarcode(String barcode) {
for (BarcodeListener listener : listeners) {
listener.onBarcodeRead(barcode);
}
}
public void addBarcodeListener(BarcodeListener listener) {
listeners.add(listener);
}
public void removeBarcodeListener(BarcodeListener listener) {
listeners.remove(listener);
}
}
In some way similar to #Cyrusmith solution I have created a 'proof of concept' solution (with several limitations right now, but you are invited to fix them :) ) trying to solve the limitations on the previous solutions in this post:
It support barcode readers that doesn't send the ENTER at the end of barcode string.
If the focus is currently on a swing text component and barcode is captured, the barcode doesn't get to the text component and only to the barcode listener.
See https://stackoverflow.com/a/22084579/320594

Categories

Resources