Writing to JEditorPane fast - java

In the code below I would like to write onto JEditorPane the numbers 0 to 10000. However, the JEditorPane does not display anything unless either it is completely done ( printing all 0 to 10000 at once) or when the os gives it time to refresh and display the contents. Either case, the application becomes unresponsive for a while that the user thinks the application has crashed. Is there anyway we can force the jEditorPane to display the updated contents, even if it is still busy ? I tried invokeAndWait but does not help here as we cannot call invokeAndWait from the event dispatcher thread.
In other words, if I replace the Thread.sleep(0) with Thread.sleep(50), it works fine and displays the print of results as they happen, but adding the 50 mill sec delay makes it extremely slow. all I want is to update the JEditorPane from time to time so that the user does not think the application crashed. I prefer to only modify the updateMessages().
Here is my code:
import javax.swing.JEditorPane;
import javax.swing.JFrame;
import javax.swing.JScrollPane;
import javax.swing.SwingUtilities;
import javax.swing.text.Document;
import javax.swing.text.html.HTMLEditorKit;
import javax.swing.text.html.StyleSheet;
public class CMessageWindowStack {
private static final String MESSAGE = "msg";
private JScrollPane scrollPane;
public JEditorPane editorPane;
private HTMLEditorKit kit;
private String msgBuffer=new String("");
private static CMessageWindowStack window=null;
private static JFrame frameContainer=null;
private CMessageWindowStack()
{
editorPane = new JEditorPane ();
editorPane.setEditable(false);
editorPane.setContentType("text/html");
kit = new HTMLEditorKit();
editorPane.setEditorKit(kit);
StyleSheet styleSheet = kit.getStyleSheet();
styleSheet.addRule("."+MESSAGE+" {font: 10px monaco; color: black; }");
Document doc = kit.createDefaultDocument();
editorPane.setDocument(doc);
scrollPane = new JScrollPane(editorPane);
}
public static CMessageWindowStack getInstance(){
if (null==window)
{window=new CMessageWindowStack();}
return window;
}
/**
* The core
* #param sMessage
* #param sType
*/
private void updateMessages(final String sMessage, final String sType)
{
SwingUtilities.invokeLater( new Runnable() {
public void run() {
String sMessageHTML="";
String sTypeText="";
if (!sMessage.equals("\r\n")){
sTypeText = sType+": ";
}
sMessageHTML = sMessage.replaceAll("(\r\n|\n)", "<br/>");
if (!sMessageHTML.equals("<br/>"))
{
sMessageHTML = "<SPAN CLASS="+sType+">"+ sTypeText+sMessageHTML + "</SPAN>";
}
msgBuffer=msgBuffer.concat( sMessageHTML);
editorPane.setText(msgBuffer);
if ((editorPane.getDocument()).getLength()>1){
editorPane.setCaretPosition((editorPane.getDocument()).getLength()-1);
}
}
});
}
public void setContainerFrame(JFrame jFrame){
frameContainer = jFrame;
javax.swing.GroupLayout layout = new javax.swing.GroupLayout(frameContainer.getContentPane());
frameContainer.getContentPane().setLayout(layout);
layout.setHorizontalGroup(
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addComponent(scrollPane)
);
layout.setVerticalGroup(
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(layout.createSequentialGroup()
.addComponent(scrollPane, javax.swing.GroupLayout.DEFAULT_SIZE, 217, Short.MAX_VALUE))
);
}
public void setVisible(boolean bVisible){
editorPane.setVisible(bVisible);
scrollPane.setVisible(bVisible);
}
public void printMsg(String sMessage){
String sType = MESSAGE;
updateMessages(sMessage,sType);
}
public void printlnMsg(String sMessage){
sMessage=sMessage.concat("\r\n");
printMsg(sMessage);
}
public static void main(String args[]){
CMessageWindowStack m_LogMgr;
JFrame frame = new JFrame();
m_LogMgr=CMessageWindowStack.getInstance();
m_LogMgr.setContainerFrame(frame);
frame.setVisible(true);
frame.setSize(500, 500);
for(int i=0;i<10000;++i){
try {
Thread.sleep(0);//becomes unresponsive if sleep(0)
} catch (Exception e) {
}
m_LogMgr.printlnMsg(i+"-----------------------");
}
}
}

You would probably need to use a SwingWorker. Start by initially loading the JEditorPane with your basic HTML. Then you would need to publish each line of data. When the data gets published you would then need to insert the text into the editor pane.
The basic idea is you can't just create an entire HTML string at once.
I've never been able to really understand how to use a JEditorPane with dynamically changing HTML. Maybe you can just use a JTextPane. It supports different fonts and colors and is much easier to update dynamically.

well - as a hack you could try something like this:
for(int i=0;i<10000;++i){
try {
if( i%100 == 0 ) {
Thread.sleep(10);//becomes unresponsive if sleep(0)
}
} catch (Exception e) {
}
m_LogMgr.printlnMsg(i+"-----------------------");
}

You can try using Thread and giving it highest priority. Also, you can add -XmX2020 option to java.exe.
Other solution may be if you use JTextArea instead of JEdiorPane. It seems like all you do with HTML is styling fonts. You can do this using java.awt.Font property on JTextArea.

Related

How to make ImageIcon on JLabel update faster with less latency

I am trying to update an ImageIcon on a JLabel which sits on a JLayeredPane, but there is a lot of latency between when the setting thread sends the proper state to the JLabel object and when the GUI displays the ImageIcon of the proper state. The following code is an example of the issue, look for the difference in time between the print of the button being on/off and when the displayed icon gets lighter/darker.
The setting thread:
new Thread(new Runnable() { // setting thread
#Override
public void run() {
// TODO Auto-generated method stub
try {
while(true) {
System.out.println("testButton on"); // print that the button is on
testButton.updateState(1); // set button state to on
Thread.sleep(70 + random.nextInt(500)); //sleep between 70 and 570 milliseconds
System.out.println("testButton off");// print that the button is off
testButton.updateState(0); // set button state to off
Thread.sleep(70 + random.nextInt(500)); // sleep between 70 and 570 milliseconds
}
} catch(Exception e) {
e.printStackTrace();
}
}
}).start();
The button object:
class Button extends JLabel {
ImageIcon released;
ImageIcon pressed;
String text;
public Button(int x, int y, String text) {
released = new ImageIcon("src/components/images/button.png");
pressed = new ImageIcon("src/components/images/buttonDown.png");
setBounds(x,y, 100, 100);
this.text = text;
setIcon(released);
}
public void updateState(int data) {
if (data == 1) {
setIcon(pressed);
}
else {
setIcon(released);
}
}
}
The ImageIcons are only 325 bytes, so what might be causing the latency? I looked up about the Event Dispatcher Thread and many people say it should be instantaneous for an image to get painted.
End goal: Have many button objects on screen with the setting thread calling them to update based on randomly occurring actions. The displayed icon for a specific button object should change immediately as it is set in the function. The setting thread will not be constantly looping, instead loop once for every action sent (it is twice here just to show the issue).
Any suggestions or things to try I will test as soon as I can.
Edit: In the end the thread that gets the information will call to a device driver in Linux where it will wait for a response and only when it gets a response will it need to update the window. From what I know timer is used to update something at regular intervals, but I am likely wrong.
As explained in the comments running long processes on the The Event Dispatch Thread blocks it, so it does not respond to changes.
Also you are not suppose to update Swing components from other (not EDT) threads.
You need to use Swing tools like SwingWorker or Timer.
The following mcve demonstrates a simple slide-show using Timer:
import java.awt.BorderLayout;
import java.io.IOException;
import java.net.URL;
import java.util.Random;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.SwingConstants;
import javax.swing.Timer;
public class ChangeButtonIcon extends JPanel{
private final URL[] urls = {
new URL("https://findicons.com/files/icons/345/summer/128/cake.png"),
new URL("http://icons.iconarchive.com/icons/atyourservice/service-categories/128/Sweets-icon.png"),
new URL("https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcS_FkBgG3_ux0kCbfG8mcRHvdk1dYbZYsm2SFMS01YvA6B_zfH_kg"),
};
private int iconNumber = 0;
private final JButton button;
private boolean stop = true;
private final Random random;
private static final int MIN_DELAY = 70, DELAY = 500;
private Timer timer;
public ChangeButtonIcon() throws IOException {
random = new Random();
button = new JButton();
button.setIcon(new ImageIcon(urls[iconNumber]));
button.setHorizontalTextPosition(SwingConstants.CENTER);
button.addActionListener(e -> startStopSlideShow());
add(button);
}
private void startStopSlideShow(){
stop = ! stop;
if(stop){
timer.stop();
return;
}
timer = new Timer( MIN_DELAY+ random.nextInt(DELAY), (e)->swapIcon());
timer.start();
}
private void swapIcon() {
iconNumber = iconNumber >= urls.length -1 ? 0 : iconNumber+1;
button.setIcon(new ImageIcon(urls[iconNumber]));
}
public static void main(String[] args) throws IOException{
JFrame window = new JFrame();
window.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
window.add(new ChangeButtonIcon());
window.add(new JLabel("Click image to start / stop"), BorderLayout.PAGE_END);
window.pack();
window.setVisible(true);
}
}

Java loading gif freeze while processing data?

I call a method which lists all the files in a directory, and adds them to a JTable:
addFilesWithSubsButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
reverseLoadingVisibility(loaderLabel); //set Visible
addFilesWithSubs2(chooser, loaderLabel);
}
});
public void addFilesWithSubs2(JFileChooser chooser, JLabel loaderLabel) {
//loading all files ....
//when every file is listed:
//Set invisible
reverseLoadingVisibility(loaderLabel);
}
The another method change reverse the visibility of the JLabel in which the loading .gif is.
public void reverseLoadingVisibility(JLabel loaderLabel) {
loaderLabel.setVisible(!loaderLabel.isVisible());
}
The problem is: the gif doesn't play, freezes while the files are added to the JTable.
UPDATE: Still have problem the loading gif freezes
addFilesButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent arg0) {
reverseLoadingVisibility(loaderLabel);
try {
new AddFiles().doInBackground(
chooser, CHOOSERTITLE,
lastDictionary,
sdf,
filesTable,
model,
columnNames,
loaderLabel);
} catch (Exception e) {
e.printStackTrace();
}
}
});
public class AddFiles extends SwingWorker{
#Override
protected Void doInBackground() throws Exception {
return null;
}
protected void doInBackground(JFileChooser chooser, String CHOOSERTITLE,
String lastDictionary,
SimpleDateFormat sdf,
JTable filesTable,
DefaultTableModel model,
String[] columnNames,
JLabel loaderLabel) throws Exception {
//Set visible
reverseLoadingVisibility(loaderLabel);
chooser.setDialogTitle(CHOOSERTITLE);
chooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);
//
chooser.setAcceptAllFileFilterUsed(true);
//TODO: this changed to chooser
if (chooser.showOpenDialog(chooser) == JFileChooser.APPROVE_OPTION) {
// create a file that is really a directory
File aDirectory = new File(chooser.getSelectedFile().toString());
lastDictionary = chooser.getSelectedFile().toString();
// get a listing of all files in the directory
String[] filesInDir = aDirectory.list();
// TODO
System.out.println("Number of files: " + filesInDir.length);
// have everything i need, just print it now
for ( int i=0; i<filesInDir.length; i++ )
{
File currentFile = new File(aDirectory + "\\" + filesInDir[i]);
System.out.println(filesInDir[i] );
System.out.println(aDirectory );
System.out.println(currentFile.length()/1024 + " KB");
System.out.println(sdf.format((currentFile).lastModified()));
// Avoid duplicates
int row = 0;
boolean duplicate = false;
for (; row < filesTable.getRowCount(); row++) {
if (model.getValueAt(row, 1).equals(filesInDir[i]) &&
model.getValueAt(row, 3).equals(aDirectory)
) {
duplicate = true;
break;
}
System.out.println("model.getValueAt(row, 1) " + model.getValueAt(row, 1));
System.out.println(filesInDir[i]);
System.out.println("model.getValueAt(row, 3) " + model.getValueAt(row, 3));
System.out.println(aDirectory);
}
if (!duplicate && currentFile.isFile()) {
model.addRow(new Object[]{
filesTable.getRowCount()+1,
filesInDir[i],
null,
aDirectory,
currentFile.length()/1024 + " KB",
sdf.format((currentFile).lastModified())
});
}
}
}
else {
System.out.println("No Selection ");
}
// Readjust columns
adjustTableColumns(filesTable, columnNames);
//Set unvisible
reverseLoadingVisibility(loaderLabel);
}
...
That's why because all files are loaded in the EDT (Event Dispatch Thread) (hopefully you launch your application using SwingUtilities.invokerLater() method) which cause all swing components to freeze. For more details read this java document by oracle: Initial Threads.
In order to solve your problem, you have to use a SwingWorker. A class responsible for heavy background tasks in Swing applications. With a simple google search, you can take an idea from here: How do I use SwingWorker in Java?
UPDATE in order to answer OP's comment.
The truth is that your code is a little bit big, and the most important, it is not an SSCCE.
In order to give you one more hand to find the solution you are looking for, i have created an SSCCE, using a SwingWorker that does something "heavy". In our case, the something heavy, is to write 1000 lines in a .txt file, but each line, thread (our worker) will sleep for 10ms.
Take it a look, run it if you want (i recommend it). Some extra comments inside the code, do not forget to check them.
package test;
import java.awt.BorderLayout;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.util.List;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.SwingUtilities;
import javax.swing.SwingWorker;
public class SwingWorkerExample extends JFrame {
/*
* Worker has Void doInBackground a.k.a, doInBackground method needs to return nothing.
* Worker needs to process-publish Integers.
*/
private SwingWorker<Void, Integer> writeToFileWorker = null;
private JLabel gifLabel;
private JButton doSomethingHeavy;
public SwingWorkerExample() {
super("Just a test.");
createWorker();
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
getContentPane().setLayout(new BorderLayout());
gifLabel = new JLabel();
ImageIcon gif = new ImageIcon("C:/Users/George/Desktop/giphy.gif");
gifLabel.setIcon(gif);
gifLabel.setVisible(false); //Initialy non visible
gifLabel.setHorizontalTextPosition(JLabel.CENTER);
gifLabel.setVerticalTextPosition(JLabel.BOTTOM);
gifLabel.setHorizontalAlignment(JLabel.CENTER);
getContentPane().add(gifLabel, BorderLayout.CENTER);
doSomethingHeavy = new JButton("Do something heavy in another thread and start dancing...");
doSomethingHeavy.addActionListener(e -> {
//Before start the worker, show gif and disable the button
doSomethingHeavy.setEnabled(false);
gifLabel.setVisible(true);
writeToFileWorker.execute();
});
getContentPane().add(doSomethingHeavy, BorderLayout.PAGE_END);
setSize(500, 300);
setLocationRelativeTo(null);
}
private void createWorker() {
writeToFileWorker = new SwingWorker<Void, Integer>() {
#Override
protected Void doInBackground() throws Exception {
File fileToWrite = new File(System.getProperty("user.home") + File.separator + "Desktop" + File.separator + "hello_worlds.txt");
try (BufferedWriter writer = new BufferedWriter(new FileWriter(fileToWrite));) {
for (int line = 0; line < 1000; line++) {
writer.append("Hello World! My name is Swing Worker.");
writer.append(System.lineSeparator());
Thread.sleep(10);
publish(line);
}
}
return null;
}
/*
* Runs in Event Dispatch Thread (EDT)
*/
#Override
protected void process(List<Integer> chunks) {
int line = chunks.get(0);//First parameter is the line
gifLabel.setText("Written " + line + " lines in the txt.");
super.process(chunks);
}
/*
* Runs in Event Dispatch Thread (EDT)
*/
#Override
protected void done() {
//When swing worker is finished, a.k.a the heavy work, stop the gif and enable the button
gifLabel.setVisible(false);
doSomethingHeavy.setEnabled(true);
super.done();
}
};
}
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> {
SwingWorkerExample swe = new SwingWorkerExample();
swe.setVisible(true);
});
}
}
Small preview:
My guess would be that your addFilesWithSubs2-Method is blocking the UI thread. If you have long running tasks, than you have to execute them in a separate thread e.g. SwingWorker

isDisplayable(),isShowing(),isVisible() giving wrong values for JDialog

Env:
OS: Win 10
IDE: Netbeans 8.1
Java: JDK 1.8u112 Oracle
jre: 10.0.2
JTattoo laf: HiFiLookAndFeel from http://www.jtattoo.net/
Context:
Creating a Look And Feel ("laf") switching class.
Desc:
There is a class A ("A") that extends JFrame("frame"). It has some GUI components like a JFileChooser("fc"), buttons etc. Its main("main") method comprises of making (inside the EDT via Swingutilities.invokeLater()("swingU")) an instace of A, setting A visible then invoking a doClick on a button(placed in the frame) that calls fc.showOpenDilaog(this). At this point a frame and fc are visible on screen.
Coming back to the main. swingU returns to main. A delay of ~3 seconds is cretaed(see code) then the laf change is caused.
laf change is caused via another class called changer. It has a method called change, that
obtains all open windows via Window.getWindows()
sorts them into Jframes, JDialogs("dialog") and remaining into Windows by checking if their class has JFrame or JDialog as a superclass.
For each dialog records its visibility,sets a titlebar, disposes it, updates its UI then restores it as per its original visibility(see code)(not related but code also does the same for the frames)
The above steps themselves are exeecuted on the EDT via swingU.
Problem:
The laf change causes fc to disappear while A stays visible with the new laf.
Expected:
both fc and A should stay visible exhibiting the new laf.
Headway into the problem:
Used System.out.println("sop")(inside change method) to show the
dialog's isDisplayable(),isShowing(),isVisible(). All of these
returned false inspite of the fact that fc was clearly visible. After loooking into their source("src"), used the
dialog.getParent()(this returned A) and soped its
isDisplayable(),isShowing(),isVisible().These returned true. Doesn't make sense!
fc.showOpenDilaog(null) makes the problem disappear.
Even tried to look through sun.awt.AppContext--could't make heads or tails.
Catch:
The changer class can't access the memebers of A e.g. fc is private in A so is button...for all intents an purposes changer doesn't know the identitiy of the frame/dialog its analyzing.
The only thing special about the given configuration of lafs(from WindowsLookAndFeel to HiFiLookAndFeel) is that the later returns true for getSupportsWindowDecorations()
Upon Andrew's advice, the SSCE
import com.jtattoo.plaf.hifi.HiFiLookAndFeel;
import com.sun.java.swing.plaf.windows.WindowsLookAndFeel;
import java.awt.Container;
import java.awt.Window;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import javax.swing.JDialog;
import javax.swing.JFrame;
import javax.swing.JRootPane;
import javax.swing.LookAndFeel;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
public class A extends javax.swing.JFrame {
public A()
{
initComponents();
}
#SuppressWarnings("unchecked")
// <editor-fold defaultstate="collapsed" desc="Generated Code">
private void initComponents() {
jFileChooser1 = new javax.swing.JFileChooser();
button = new javax.swing.JButton();
setDefaultCloseOperation(javax.swing.WindowConstants.EXIT_ON_CLOSE);
button.setText("jButton1");
button.addActionListener(new java.awt.event.ActionListener() {
public void actionPerformed(java.awt.event.ActionEvent evt) {
buttonActionPerformed(evt);
}
});
javax.swing.GroupLayout layout = new javax.swing.GroupLayout(getContentPane());
getContentPane().setLayout(layout);
layout.setHorizontalGroup(
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(layout.createSequentialGroup()
.addGap(128, 128, 128)
.addComponent(button)
.addContainerGap(195, Short.MAX_VALUE))
);
layout.setVerticalGroup(
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(layout.createSequentialGroup()
.addGap(71, 71, 71)
.addComponent(button)
.addContainerGap(197, Short.MAX_VALUE))
);
pack();
}// </editor-fold>
private void buttonActionPerformed(java.awt.event.ActionEvent evt) {
jFileChooser1.showOpenDialog(this);
//jFileChooser1.showOpenDialog(null) has no problem
}
public static void main(String args[]) throws ClassNotFoundException, InstantiationException, IllegalAccessException, UnsupportedLookAndFeelException, InterruptedException, InvocationTargetException {
UIManager.setLookAndFeel(new HiFiLookAndFeel());
SwingUtilities.invokeLater(new Runnable() {
public void run() {
try {
A x = new A();
x.setVisible(true);
x.button.doClick();
} catch (Exception ex) {
ex.printStackTrace();
}
}
});
Thread.sleep(3000);
changer.change(new WindowsLookAndFeel());
}
// Variables declaration - do not modify
private javax.swing.JButton button;
private javax.swing.JFileChooser jFileChooser1;
// End of variables declaration
}
class changer {
public static void change(LookAndFeel laf) throws UnsupportedLookAndFeelException, InterruptedException, InvocationTargetException {
UIManager.setLookAndFeel(laf);
SwingUtilities.invokeLater(new Runnable() {
public void run() {
ArrayList<JFrame> frames = new ArrayList<>();
ArrayList<JDialog> dialogs = new ArrayList<>();
ArrayList<Window> winds = new ArrayList<>();
Window[] wins = Window.getWindows();
if (wins == null || wins.length == 0) {
return;
}
for (int i = 0; i < wins.length; i++) {
Class cls = wins[i].getClass();
if (JFrame.class.isAssignableFrom(cls)) {
frames.add((JFrame) wins[i]);
} else if (JDialog.class.isAssignableFrom(cls)) {
dialogs.add((JDialog) wins[i]);
} else {
winds.add(wins[i]);
}
}
boolean initialState = false;
if (frames.size() > 0) {
for (JFrame fr : frames) {
fr.getRootPane().setWindowDecorationStyle(JRootPane.NONE);
initialState = fr.isVisible();
fr.dispose();
fr.setUndecorated(false);
SwingUtilities.updateComponentTreeUI(fr);
fr.pack();
fr.setVisible(initialState);
}
}
if (dialogs.size() > 0) {
for (JDialog dia :dialogs) {
dia.getRootPane().setWindowDecorationStyle(JRootPane.FRAME);
Container par = dia.getParent();
sop("par----");
sop(par);
sop("disp:" + par.isDisplayable());
sop("vis:" + par.isVisible());
sop("show:" + par.isShowing());
sop("dia----");
sop("disp:" + dia.isDisplayable());
sop("vis:" + dia.isVisible());
sop("show:" + dia.isShowing());
initialState = dia.isVisible();
dia.dispose();
dia.setUndecorated(false);
SwingUtilities.updateComponentTreeUI(dia);
dia.pack();
dia.setVisible(initialState);
}
}
}
});
}
public static void sop(Object obj)
{
System.out.println(obj);
}
}

Accessible screenreading of JavaFX "console" output

I'm trying to create a simple UI that is accessible for screenreaders. I've been mostly successful, but I can't manage to design the UI in a way that has the screenreader read new text output.
Currently, I have a TextArea displaying the output of an anonymous PrintStream created and set by System.setOut. Sometimes I open up a TextField for string inputs, but I've been working with just the TextArea to test the reading of text (for now it just listens for keystrokes to display more text for testing purposes).
The issue is this: when new text is added via System.out to the TextArea, the screenreader does not read it. I am still able to navigate upward with the arrow keys to read what was added but it is not read when first added. Is there any way to get the screenreader to treat my TextArea more like a standard console (in which it reads all new text automatically)? I'm using NVDA.
Things I have tried:
- Using TextArea.notifyAccessibleAttributeChanged(AccessibleAttribute.TEXT)
- Using TextArea.requestFocus() and TextArea.notifyAccessibleAttributeChanged(AccessibleAttribute.FOCUS_NODE)
- Disabling autoflush on the PrintStream while using TextArea.setAccessibleText(theNewText) during a flush
- Using a hidden Label set to the new text and focusing on it (I'm still fiddling with this one; Screenreaders can't read actual "hidden" text so I'm trying to find a way to draw it but also be "invisible", perhaps behind the TextArea somehow)
- Changing focus to another Node and back, which doesn't work as I like because it reads the other Nodes accessible stuff and then reads the entire body of the TextArea
- Various combinations of these
I just can't seem to get it to work. I feel like I'm missing something simple and obvious here, but the JavaFX Accessibility API is still relatively new and I can't find solutions to specific problems like this one.
Here's the relevant code of my Application, if it helps any:
#Override
public void start(Stage primaryStage) {
try {
primaryStage.setTitle("Test");
root = new BorderPane();
root.setFocusTraversable(false);
Scene scene = new Scene(root,800,600);
scene.getStylesheets().add(getClass().getResource("application.css").toExternalForm());
primaryStage.setScene(scene);
//Create middle
output = new TextArea();
output.setEditable(false);
output.setFocusTraversable(false); //I've tried true also, just to test
output.setAccessibleRole(AccessibleRole.TEXT_AREA);
root.setCenter(output);
...
//Begin
primaryStage.show();
Thread th = new Thread(new AppMain());
th.start();
} catch(Exception e) {
e.printStackTrace();
}
}
#Override
public void init() {
//Set output to TextArea
System.setOut(new PrintStream(new OutputStream() {
#Override
public void write(int b) throws IOException {
appendTextArea(String.valueOf((char) b));
}
}, true)); //I've also overriden flush while this is false, see above
}
public void appendTextArea(String str) {
Platform.runLater(() -> {
output.appendText(str);
});
}
I seriously appreciate any help or suggestions you can provide. I've been messing with this small issue for way too long, and I'm still new to JavaFX. Thank you!
Here is a full working example based off of your code.
Disclosure: For the screen reader I'm using the "Voice Over Utility" on my Mac, but hopefully that doesn't differ too much from your environment.
The key was to utilize the Control#executeAccessibleAction method.
Example:
TextArea.executeAccessibleAction(AccessibleAction.SET_TEXT_SELECTION, start, finish);
The Application Class
package application;
import java.io.PrintStream;
import java.io.IOException;
import java.io.OutputStream;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.stage.Stage;
import javafx.scene.AccessibleAction;
import javafx.scene.AccessibleRole;
import javafx.scene.Scene;
import javafx.scene.control.TextArea;
import javafx.scene.layout.BorderPane;
public class Main extends Application
{
private TextArea output;
private BorderPane root;
private final StringBuilder STR_BUFFER = new StringBuilder();
private static final String NEW_LINE = System.lineSeparator();
#Override
public void start(Stage primaryStage)
{
try
{
primaryStage.setTitle("Test");
root = new BorderPane();
root.setFocusTraversable(false);
Scene scene = new Scene(root, 800, 600);
scene.getStylesheets().add(getClass().getResource("application.css").toExternalForm());
primaryStage.setScene(scene);
// Create middle
output = new TextArea();
output.setEditable(false);
output.setFocusTraversable(true); // I've tried true also, just to test
// ----------------------------------------------
// Tell the Screen Reader what it needs to access
// ----------------------------------------------
output.setAccessibleRole(AccessibleRole.TEXT_AREA);
root.setCenter(output);
// ...
// Begin
primaryStage.show();
// start the thread
Thread th = new Thread(new AppMain());
th.start();
}
catch (Exception e)
{
e.printStackTrace();
}
}
#Override
public void init()
{
// Set output to TextArea when we have a full string
System.setOut(new PrintStream(new OutputStream()
{
#Override
public void write(int b) throws IOException
{
if (b == '\r')
{
return;
}
if (b == '\n')
{
final String text = STR_BUFFER.toString() + NEW_LINE;
appendTextArea(text);
STR_BUFFER.setLength(0);
}
else
{
STR_BUFFER.append((char) b);
}
}
}, true));
}
public void appendTextArea(String str)
{
Platform.runLater(() ->
{
int anchor = output.getText().length();
output.appendText(str);
// just to clear it
output.positionCaret(0);
// ----------------------------------------------
// Tell the Screen Reader what it needs to do
// ----------------------------------------------
output.executeAccessibleAction(AccessibleAction.SET_TEXT_SELECTION, anchor, anchor + str.length());
});
}
public static void main(String[] args)
{
launch(args);
}
}
The Thread Class
/*
* Just to simulate a feed to the console (textArea).
* This will die after 1 minute.
*/
package application;
public class AppMain implements Runnable
{
#Override
public void run()
{
int i = 0;
long start = System.currentTimeMillis();
while (System.currentTimeMillis() - start < 60000)
{
try
{
Thread.sleep(3000);
}
catch (InterruptedException e)
{}
System.out.println("This is line number " + ++i);
}
}
}

Clickable thumbnail image in a tooltip

I would like to have a special sort of tooltip appear when a user hovers the mouse cursor over a specific item in a treetable. This tooltip will be a thumbnail of a PDF that corresponds to the item in the treetable that the cursor is pointing at. In addition, I'd like the user to be able to then move the cursor over the thumbnail and click it, which should open up the full PDF in their system's default PDF reader (Acrobat, Adobe Reader, etc.).
I realize this is a tall order, but I've already done most of the work. I've discovered exactly where in my huge program that I need to have the setToolTip() method so that it can retrieve the appropriate thumbnail. In addition, since I've discovered that having Java create thumbnails from a PDF on the fly is far too difficult, I've already got things set up so there will be thumbnail JPGs pre-made. Thus, all the setToolTip() command will need to do is somehow retrieve the appropriate JPG. Now comes the hard part.
At first, it seemed easy. I tried this really convenient hack for putting an image in a tooltip, and it definitely gets the thumbnail showing up properly. However, surrounding the <img> tag with an anchor tag (...) doesn't quite seem to work. The thumbnail is surrounded by the tell-tale blue border, alright, but the image remains un-clickable. In addition, the tooltip sometimes just disappears before it's image can be clicked upon.
So I thought I might need to do something more deep than a simple html hack. I tried this more involved way of putting an image in a tooltip, but it seems that will only work for a static image. I need the image to be different depending on what's being hovered over with the mouse cursor. In addition, how do I set my method to use this 'custom version of a tooltip' rather than the built-in one?
To give a bit more context, the location where the setToolTip() method seems to work is inside of a getTreeCellRendererComponent() method, a part of a custom class that extends JPanel and implements TreeCellRenderer. I'll post the code if asked, but it will might be rather complicated and hard to follow. Any thoughts?
EDIT 10/09/2014, 4:57pm: Much of this code may be confusing, and for that, I apologize. Suffice it to say that it has to do with putting a tri-state checkbox inside of a JXTreeTable. Anyway, the parts that are important should be easy enough to pick out, I hope. As you can see, this class already extends JPanel, so I cannot have it extend JToolTip as well.
package info.chrismcgee.sky.treetable;
import info.chrismcgee.beans.OrderDetail;
import info.chrismcgee.components.ImageToolTip;
import info.chrismcgee.components.TristateCheckBox;
import info.chrismcgee.components.TristateState;
import info.chrismcgee.enums.OSType;
import java.awt.BorderLayout;
import java.io.File;
import javax.swing.JComponent;
import javax.swing.JPanel;
import javax.swing.JToolTip;
import javax.swing.JTree;
import javax.swing.tree.TreeCellRenderer;
import javax.swing.tree.TreePath;
import org.jdesktop.swingx.treetable.DefaultMutableTreeTableNode;
public class SkyCheckTreeCellRenderer extends JPanel implements
TreeCellRenderer {
/**
*
*/
private static final long serialVersionUID = -2728513730497144120L;
private SkyCheckTreeSelectionModel selectionModel;
private TreeCellRenderer delegate;
private boolean showRootNodeCheckBox;
private TristateCheckBox checkBox = new TristateCheckBox("");
protected SkyCheckTreeManager.CheckBoxCustomizer checkBoxCustomer;
private String jobsFolderStr = OSType.getOSType() == OSType.MAC
? "/Volumes/ArtDept/ArtDept/JOBS"
: "//SKYFS/ArtDept/ArtDept/JOBS";
public SkyCheckTreeCellRenderer(TreeCellRenderer delegate,
SkyCheckTreeSelectionModel selectionModel,
boolean showRootNodeCheckBox) {
this.delegate = delegate;
this.selectionModel = selectionModel;
this.showRootNodeCheckBox = showRootNodeCheckBox;
setLayout(new BorderLayout());
setOpaque(false);
checkBox.setOpaque(false);
}
public JToolTip createToolTip() {
return new ImageToolTip();
}
private String getToolTipText(DefaultMutableTreeTableNode node)
{
if (node.getUserObject() instanceof OrderDetail)
{
OrderDetail od = (OrderDetail) node.getUserObject();
String thousandsFolderStr = jobsFolderStr + "/"
+ od.getOrderId().substring(0, 3) + "000-"
+ od.getOrderId().substring(0, 3) + "999/";
String productFolderStr = thousandsFolderStr + od.getOrderId()
+ " Folder/";
if (!od.getProductDetail().equals(""))
productFolderStr = thousandsFolderStr + od.getOrderId() + "/";
String img = productFolderStr + od.getOrderId() + "_THUMB.jpg";
if (!od.getProductDetail().equals(""))
img = productFolderStr + od.getOrderId() + "_" + od.getProductDetail() + "_THUMB.jpg";
if (new File(img).exists())
return "<html><img src=\"file://" + img + "\"></html>";
}
return null;
}
public JComponent getTreeCellRendererComponent(JTree tree, Object value,
boolean selected, boolean expanded, boolean leaf, int row,
boolean hasFocus)
{
JComponent renderer = (JComponent) delegate.getTreeCellRendererComponent(tree, value,
selected, expanded, leaf, row, hasFocus);
if (!showRootNodeCheckBox && tree.getModel().getRoot() == value)
{
renderer.setToolTipText(getToolTipText((DefaultMutableTreeTableNode)value));
return renderer;
}
TreePath path = tree.getPathForRow(row);
if (path != null) {
if (checkBoxCustomer != null && !checkBoxCustomer.showCheckBox(path))
{
renderer.setToolTipText(getToolTipText((DefaultMutableTreeTableNode)value));
return renderer;
}
if (selectionModel.isPathSelected(path, selectionModel.isDigged()))
checkBox.getTristateModel().setState(TristateState.SELECTED);
else
checkBox.getTristateModel().setState(selectionModel.isDigged()
&& selectionModel.isPartiallySelected(path)
? TristateState.INDETERMINATE
: TristateState.DESELECTED);
}
removeAll();
add(checkBox, BorderLayout.WEST);
add(renderer, BorderLayout.CENTER);
setToolTipText(getToolTipText((DefaultMutableTreeTableNode)value));
return this;
}
}
I get that I need to somehow extend JToolTip, and that this SkyCheckTreeCellRenderer class needs to somehow reference that custom tooltip. I guess all of this is just getting so involved and complex that my simple brain is having trouble wrapping around it all. My apologies.
how do I set my method to use this 'custom version of a tooltip' rather than the built-in one?
As the example shows you need to extend the component to use the custom tool tip.
I need the image to be different depending on what's being hovered over with the mouse cursor
Then you will need to override the getToolTipText(MouseEvent) method to return a text string to represent the image you want to display.
However, surrounding the tag with an anchor tag (...) doesn't quite seem to work
You would need to use a JEditorPane if you want to respond to a hyperlink. Read the JEditorPane API for an example.
So basically I would suggest that you need to use a custom JToolTip, that uses a JEditorPane to display the appropriate Image with an appropriate Hyperlink. Here is an example that shows how to use a JLabel as an added component to a tool tip. You should be able to modify the code to use a JEditorPane.
Also, you need to extend your tree table to use this custom JToolTip.
import java.awt.*;
import java.awt.image.BufferedImage;
import java.net.URL;
import java.io.*;
import javax.imageio.ImageIO;
import javax.swing.*;
public class ToolTipImage extends JToolTip
{
private Image image;
public ToolTipImage(Image image)
{
this.image = image;
setLayout( new BorderLayout() );
add( new JLabel( new ImageIcon( image) ) );
}
#Override
public Dimension getPreferredSize()
{
return new Dimension(image.getWidth(this), image.getHeight(this));
}
private static void createAndShowGUI() throws Exception
{
final BufferedImage testImage = ImageIO.read(new File("dukewavered.gif"));
String[] columnNames = {"Column 0", "Column 1"};
Object[][] data =
{
{"Cell 0,0", "Cell 0,1"},
{"Cell 1,0", "Cell 1,1"}
};
JTable table = new JTable(data, columnNames)
{
public JToolTip createToolTip()
{
return new ToolTipImage( testImage );
}
};
// Set tool tip text so that table is registered w/ tool tip manager
table.setToolTipText(" ");
JFrame frame = new JFrame("Tool Tip Image");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add( new JScrollPane(table) );
frame.setLocationByPlatform( true );
frame.pack();
frame.setVisible( true );
}
public static void main(String[] args)
{
EventQueue.invokeLater(new Runnable()
{
public void run()
{
try
{
createAndShowGUI();
}
catch(Exception e) { System.out.println(e); }
}
});
}
}
It sounds like you need to build a custom tooltip, as detailed in JToolTip.
When clicked, you should use Runtime to open your file from the commandline. The way to do this in windows is posted here. The way to do this on ubuntu is posted here

Categories

Resources