In most text editors and platforms there are two ways of selecting text using the mouse:
The regular, Click+Drag, moves the end of the selection along with the mouse cursor
Double-click+Drag, same as #1 but it starts by selecting one whole word, and then snaps the end of the selection to whole words
In Swing GUI however, #2 does not work as above. It starts correctly by selecting the whole word where the double-click was, but then it does not snap to whole words during selection.
Is there any way to get Swing text fields to behave as 2, with the selection snapping to whole words?
You can create a method to calculate the index of where word your selection ends and starts. See below:
int getWordEndPos(String text, int initPos) {
int i = initPos;
while(Character.isAlphabetic(text.charAt(i))) {
i++;
}
return i;
}
int getWordStartPos(String text, int initPos) {
int i = initPos;
while(Character.isAlphabetic(text.charAt(i))) {
i--;
}
return i+1;
}
Then in your UI (not sure exactly how JTextArea works) you could get the start and end position of your selection, and actually selects the start and end position of their words:
void updateSelection(JTextArea ta) {
String text = ta.getText();
int start = ta.getSelectionStart();
int end = ta.getSelectionEnd();
start = getWordStartPos(text, start);
end = getWordEndPos(text, end);
ta.select(start, end);
}
But where to call the snippet above? You could listen to CarretEvent instead of MouseEvent (see Which event a selection of text trigger in Java JTextArea?):
textArea.addCarretListener((evt) -> updateSelection(textArea));
But another problem arrises: how to know the click count of MouseEvent. You could make an attribute to store it, and then into the mouse event listener, it can be set. The code below tries to put everything toghether:
class UI implements MouseListener, CarretListener {
JTextArea textArea;
int clickCount = 0;
UI() {
textArea.addCarretListener(this);
textArea.addMouseListener(this);
// ...
}
#Override
void mouseClicked(MouseEvent evt) {
this.clickCount = evt.getClickCount();
// other stuff
}
// other MouseListener methods
#Override
void caretUpdate(CaretEvent evt) {
if (clickCount == 1) updateSelection(textArea);
// other caret listener stuff
}
void updateSelection(JTextArea ta) {
String text = ta.getText();
int start = ta.getSelectionStart();
int end = ta.getSelectionEnd();
start = getWordStartPos(text, start);
end = getWordEndPos(text, end);
ta.select(start, end);
}
}
Related
I'm working on a personal project where I'm trying to create Simon game in Java. It is essentially a memory game where the user has to repeat the sequence the computer generates. So, I created a basic CLI version of the game and now I'm looking to give it a GUI. I haven't worked with Java Swing before so I'm having some trouble replicating the same behavior in GUI form.
The structure of the CLI version looked something like this:
public static void main(String[] args) {
GameContext simon = new GameContext();
simon.start();
while (!simon.getRoundEndState()) {
simon.playSequence();
simon.pickColour();
}
}
Here's what the playSequence() method looks like. It essentially generates a number between 0 and 3 and keeps adding one number to the array each round if the player gets the previous one right.
public void playSequence() {
int rand = getRandomNumber(4);
computerSequence.add(rand);
for(int i:computerSequence) {
System.out.println(i);
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
gameContext.setState(gameContext.getHumanPlayingState());
}
pickColour() method that asks the user for the pattern and checks if the pattern matches the one generated by the computer.
public void pickColour() {
boolean roundWon = true;
for(int i=0; i<gameContext.getSequence().size();i++){
Scanner input = new Scanner(System.in);
Integer userInput = input.nextInt();
if(userInput == gameContext.getSequence().get(i)) {
continue;
}
else {
System.out.println("Input mismatched the sequence");
roundWon = false;
break;
}
}
if (roundWon == true) {
score++;
gameContext.setState(gameContext.getComputerPlayingState());
} else {
gameContext.setRoundEndState(true);
System.out.println("Your score is: " + score);
gameContext.setState(gameContext.getInLobbyState());
}
}
Please note that I'm using the state pattern here and hence the change in states. I got the first part working where I need to light up the colors after the computer generates the the sequence. So now, playSequence() looks something like this:
public void playSequence() {
int rand = getRandomNumber(4);
computerSequence.add(rand);
gameContext.setState(gameContext.getHumanPlayingState());
}
And I added a mouse listener to the start button which looks something like this:
btnStart.addMouseListener(new MouseAdapter() {
#Override
public void mouseClicked(MouseEvent me) {
simon = new GameContext();
simon.start();
while (!simon.getRoundEndState()) {
simon.playSequence();
for(int i : simon.getSequence()) {
lightPanels(i);
Timer timer = new Timer(1000, e -> {
darkenPanels(i);
});
timer.setRepeats(false);
timer.start();
}
simon.pickColour();
}
}
});
I have 4 JPanels that now act as the input buttons. How do I change simon.pickColour() so that instead the asking the user for the correct sequence like it did in the CLI version, it would register the clicks made in the JPanels as the input.
It essentially generates a number between 0 and 3 and keeps adding one number to the array each round if the player gets the previous one right.
and
I have 4 JPanels that now act as the input buttons.
Why have 4 panels. Just have one panel that contains 4 buttons. You would then add an ActionListener to the button (not a MouseListner) to handle the clicking of the button.
The buttons would be created with code like:
for (int i = 1; i <= 4; i++)
{
JButton button = new JButton("" + i);
button.addActionListener(...);
panel.add( button );
}
The ActionListener code would then get the text of the button and add the text to an ArrayList to track the order the the buttons that have been clicked.
A working example of this approach of sharing an ActionListener for all the buttons can be found here: https://stackoverflow.com/a/33739732/131872. Note the "action command" will default from the text that is set on the button.
I am working on doing a word finder puzzle game. When I am trying to get to happen is a user clicks on a letter then moves his mouse across other letters to create a word. I am having some problems with the listeners. I have been going back and fourth using mouseDragged and mouseMoved. So far mouseMoved seems to work better because it dynamically grabs values. The problem is I can't figure out how to get it only grab one value. In an ideal world it would move of a Button or label grab that value once and ignore the value till it reaches a new button or label. Currently it just grabs values at every instant a mouse is on that container. The logic for my Mouse method is below:
public void mouseMoved(MouseEvent e) {
int count = countClicked;
int num = 0;
for(JToggleButton row : puzzleGrid){
if(e.getComponent() == row && count == 1) {
if(num == 0){
num++;
for(JLabel l: solWords)
{
sb.append(row.getText());
System.out.println(l.getText()+" = "+ sb.toString());
if(l.getText().contentEquals(row.getText()))
System.out.println(row.getText());
}
}
}
}
}
I am using the value gathered from the containers to check against an array of JLabels containing the solution values.
You could store the last letter in a static variable:
static String lastLetter = null;
mouseMoved(...) {
if(row.getText().equals(lastLetter)) {
continue;
}
lastLetter = row.getText();
}
I'm trying to add functionality to a Swing JLabel and JTextArea such that:
The user is only allowed to enter 500 characters into the textarea (max)
The label contains a string message telling the user how many characters they have left (after every key stroke or backspace)
When the components initialize the label reads "500 characters maximum!"
For the first 500 characters typed, for every keystroke (a - z, A - Z, 0 - 9, and punctuation) typed, the label reads "x characters remaining", where x is the number of chars they have left before they reach the max of 500
When the 500th character is typed, the label reads "0 characters remaining", and no further characters can be typed into the text area
If the user types the backspace button (KeyEvent.VK_BACK_SPACE), they "free" up a character, and the count increments. Thus if they had 400 characters remaining, and they type backspace, the label now reads "401 characters remaining"
If the user highlights a set of characters and performs a bulk command on them (such as a backspace, or replacing the highlighted text with a single character), the correct # of chars remaining will be calculated correctly and the label will be updated. So if they have 50 chars remaining, and they highlight 5 letters and hit backspace, they now have "55 characters remaining"
I have 90% of this functionality working, but have a few bugs, and have no clue as to how to implement the last item above (bulk commands on highlighted text). Here's what I have:
boolean ignoreInput = false;
int charMax = 500;
JLabel charCntLabel = getLabel();
JTextArea myTextArea = getTextArea();
myTextArea.addKeyListener(new KeyListener() {
#Override
public void keyTyped(KeyEvent e) {
return;
}
#Override
public void keyReleased(KeyEvent e) {
// If we should be ignoring input then set make sure we
// enforce max character count and remove the newly typed key.
if(ignoreInput)
myTextArea.setText(myTextArea.getText().substring(0,
myTextArea.getText().length()));
}
#Override
public void keyPressed(KeyEvent e) {
String charsRemaining = " characters remaining";
int newLen = 0;
// The key has just been pressed so Swing hasn't updated
// the text area with the new KeyEvent.
int currLen = myTextArea.getText().length();
// Adjust newLen depending on whether the user just pressed
// the backspace key or not.
if(e.getKeyCode() == KeyEvent.VK_BACK_SPACE) {
newLen = currLen - 1;
ignoreInput = false;
}
else
newLen = currLen + 1;
if(newLen < 0)
newLen = 0;
if(newLen == 0)
charCntLabel.setText(charMax + " characters maximum!");
else if(newLen >= 0 && newLen < charMax)
charCntLabel.setText((charMax - newLen) + charsRemaining);
else if(newLen >= charMax) {
ignoreInput = true;
charCntLabel.setText("0 " + charsRemaining);
}
}
});
The above code works pretty well, but has a few bugs:
It doesn't prevent the user from typing in > 500 characters. When the user types in the 500th character, the label reads "0 characters remaining." But you can continue to type in characters after that, and the label stays the same.
If you have > 500 characters in the textarea, and you start backspacing, you'll see each character being removed from the textarea (the underlying model), but the label stays the same. But, once you backspace enough to get to the 500th character, and you backspace, the label will start changing properly, telling you that you have "1 characters remaining", "2 characters remaining", etc. So...
This code seems to work but just stops working > 500 characters. Once you get back inside that 500 char max, it begins working again.
The questions
Is there a simpler way to implement this desired functionality (and for a Swing JTextArea)? I feel like I'm reinventing the wheel here and that there might be a "cleaner" way of enforcing character maximums and updating their respective labels.
If not, can anybody spot my > 500 char bug? I've been looking at it all morning and am pulling my hair out.
Most importantly, how do I implement my requirement to handle bulk commands to highlighted text? How do I hand text selections inside the textarea, listen for changes to the highlighted text (e.g., deleting multiple highlighted characters with the backspace button, etc.), and correctly calculate the new value for chars remaining?
Thanks in advance.
You can limit the max size by using a DocumentFilter, check this documentation section, it has a working example of what you need.
Take this as an example, I used the component from the example file above:
import java.awt.BorderLayout;
import javax.swing.*;
import javax.swing.event.*;
import javax.swing.text.*;
import components.DocumentSizeFilter;
public class Test {
public static void main(String[] args) {
new TestFrame().setVisible(true);
}
private static class TestFrame extends JFrame{
private JTextField textField;
private DefaultStyledDocument doc;
private JLabel remaningLabel = new JLabel();
public TestFrame() {
setLayout(new BorderLayout());
textField = new JTextField();
doc = new DefaultStyledDocument();
doc.setDocumentFilter(new DocumentSizeFilter(500));
doc.addDocumentListener(new DocumentListener(){
#Override
public void changedUpdate(DocumentEvent e) { updateCount();}
#Override
public void insertUpdate(DocumentEvent e) { updateCount();}
#Override
public void removeUpdate(DocumentEvent e) { updateCount();}
});
textField.setDocument(doc);
updateCount();
add(textField, BorderLayout.CENTER);
add(remaningLabel, BorderLayout.SOUTH);
setLocationRelativeTo(null);
pack();
}
private void updateCount()
{
remaningLabel.setText((500 -doc.getLength()) + " characters remaining");
}
}
}
evt.consume(); will help alot in this case..you dont need to use DocumentFilter. here is a much easier way of limiting user at certain length
private void jTextArea1KeyTyped(java.awt.event.KeyEvent evt) {
String s=jTextArea1.getText();
int l=s.length();
jTextField1.setText(String.valueOf(l));
int i=10-l;
jTextField2.setText(String.valueOf(i));
try{
if(l>=10){evt.consume();
}
}
catch(Exception w){}
}
To add on to what Ray S. Kan said:
There is an easier way to limit the characters for a JTextArea without having to use a DocumentFilter as shown by Ray S. Kan, but the issue with his answer is that it does not prevent someone from pasting in a long text. The following will prevent a user from pasting in stuff to bypass the limit:
#Override
public void keyTyped(KeyEvent e) {
int max = 25;
if(text.getText().length() > max+1) {
e.consume();
String shortened = text.getText().substring(0, max);
text.setText(shortened);
}else if(text.getText().length() > max) {
e.consume();
}
}
This will stop a key from being pressed if the length is not pass max, but if it passes max, it will simply replace the string in the text area with a shorter string. The text variable is the JTextArea swing object.
I am busying writing a simple application using JavaFX2. The goal is just to plot 2 nodes (the nodes are movable by dragging them) and then have a function to draw lines between these nodes.
I finished the functions to add and move nodes (at the moment I am just using Ellipse shapes but I am going to replace it later with my own node class) but now I am struggling with the connecting lines. The actions to add a node or a line is from a dropdown menu and I have the following code on the line function:
private void drawLine(MenuItem line) {
final BooleanProperty lineActive = new SimpleBooleanProperty(false);
final BooleanProperty clickOne = new SimpleBooleanProperty(false);
final BooleanProperty clickTwo = new SimpleBooleanProperty(false);
line.setOnAction(new EventHandler<ActionEvent>() {
public void handle(ActionEvent t) {
lineActive.set(true);
}
});
nodeGroup.setOnMousePressed(new EventHandler<MouseEvent>() {
public void handle(final MouseEvent t1) {
clickOne.set(true);
if (lineActive.get()) {
if (clickOne.get()) {
//get x and y of first node
x1 = ((Ellipse) t1.getTarget()).getCenterX();
y1 = ((Ellipse) t1.getTarget()).getCenterY();
clickOne.set(false);
clickTwo.set(true);
}
if (clickTwo.get()) {
nodeGroup.setOnMouseClicked(new EventHandler<MouseEvent>() {
public void handle(MouseEvent t2) {
//get x and y of second node
x2 = ((Ellipse) t2.getTarget()).getCenterX();
y2 = ((Ellipse) t2.getTarget()).getCenterY();
//draw line between nodes
final Line line = new Line();
line.setStartX(x1);
line.setStartY(y1);
line.setEndX(x2);
line.setEndY(y2);
canvas.getChildren().add(line);
clickTwo.set(false);
lineActive.set(false);
}
});
}
}
}
});
}
I just have the booleans to check for the first and second click to get the center of each node.
My first question is when I click on the line function and add a line between 2 nodes, it doesn't seem to end the function, and any other nodes I click on gets a line to it. How can I prevent it from executing more than once.
And my second question is how can I "connect" the line to the nodes that if the node moves, the line stays in the center of the node?
Thanks.
I think there is a couple of things which could make this simpler...
Do not use booleans when you have more than two states (no click, click one, click two) use an enum instead. Then you only need one variable to look after.
Only ever set one mouse listener on the nodeGroup and check which state you're in and have the appropriate code there instead of separate mouse listeners.
I imagine that the program is setting the listener for the second click and not resetting it to the listener for the first click when it completes.
Since i am new to Stack Overflow , I tried something on your problem
First add line between two labels
final Line line = new Line();
#Override
public void initialize(URL arg0, ResourceBundle arg1)
{
// TODO Auto-generated method stub
line.setStartX(lblDragTest.getLayoutX());
line.setStartY(lblDragTest.getLayoutY());
line.setEndX(lblNew.getLayoutX());
line.setEndY(lblNew.getLayoutY());
rootAnchorPane.getChildren().add(line);
}
And then add this methods...
// This code handles label move
//set lblDragMousePressed method to Mouse Pressed event for lblDrag
#FXML
public void lblDragMousePressed(MouseEvent m)
{
System.out.println("Mouse is pressed");
prevLblCordX= (int) lblDragTest.getLayoutX();
prevLblCordY= (int) lblDragTest.getLayoutY();
prevMouseCordX= (int) m.getX();
prevMouseCordY= (int) m.getY();
}
//set this method on mouse released event for lblDrag
#FXML
public void lblDragMouseReleased(MouseEvent m)
{
System.out.println("Label Dragged");
}
// set this method on Mouse Drag event for lblDrag
#FXML
public void lblDragMouseDragged(MouseEvent m)
{
diffX= (int) (m.getX()- prevMouseCordX);
diffY= (int) (m.getY()-prevMouseCordY );
int x = (int) (diffX+lblDragTest.getLayoutX()-rootAnchorPane.getLayoutX());
int y = (int) (diffY+lblDragTest.getLayoutY()-rootAnchorPane.getLayoutY());
if (y > 0 && x > 0 && y < rootAnchorPane.getHeight() && x < rootAnchorPane.getWidth())
{
lblDragTest.setLayoutX(x);
lblDragTest.setLayoutY(y);
}
line.setStartX(lblDragTest.getLayoutX());
line.setStartY(lblDragTest.getLayoutY());
line.setEndX(lblNew.getLayoutX());
line.setEndY(lblNew.getLayoutY());
// rootAnchorPane.getChildren().add(line);
}
In my application four TextArea is there and I want to enter only four character in one Text area and cursor automatically move to next TestArea. Again when I enter four character in this TextArea then again cursor automatically move to next TextArea.
Example: At the time of installing Window XP it want "Key" and there are four section when you enter four character in first section then cursor automatically move to the next section.
Same thing I want in my application.
For this first of all I add CustomizedTextFields.jar and then created four IntegerField:
private IntegerField text1;
private IntegerField text2;
private IntegerField text3;
private IntegerField text4;
after this I show all these IntegerField on my frame.
Now I tried this code to send cursor to the next field but it's not working:
text1.addKeyListener(new KeyListener() {
#Override
public void keyTyped(KeyEvent e) {
int a2 = text1.getText().length();
if (a2 == 3) {
text2.getCursor();
}
}
#Override
public void keyReleased(KeyEvent e) {
}
#Override
public void keyPressed(KeyEvent e) {
}
});
interesting enough question to try improving my shadowy knowledge of the text package :-)
There are two separate requirements here
restrict the lenght of the text: that's done with a DocumentFilter as #mKorbel already noted
automatically transferFocus to the next component after the max length is reached: turns out that can be done with a NavigationFilter
in code:
JComponent panel = new JPanel();
final int maxSize = 3;
for (int i = 0; i < 4; i++) {
final JTextField field = new JTextField(5);
NavigationFilter filter = new NavigationFilter() {
#Override
public void setDot(FilterBypass fb, int dot, Bias bias) {
if (dot >= maxSize) {
fb.setDot(0, bias);
field.transferFocus();
return;
}
fb.setDot(dot, bias);
}
#Override
public void moveDot(FilterBypass fb, int dot, Bias bias) {
if (dot >= maxSize) {
fb.setDot(0, bias);
field.transferFocus();
return;
}
fb.moveDot(dot, bias);
}
};
field.setNavigationFilter(filter);
((AbstractDocument) field.getDocument()).setDocumentFilter(new DocumentSizeFilter(maxSize));
panel.add(field);
}
The documentFilter is the one from the Swing Tutorial
At the time of installing Window XP it want "Key" and there are four section
when you enter four character in first section then cursor automatically move
to the next section.
add DocumentListener to the JTextComponents, for listening add DocumentFilter
don't use KeyListener for JTextComponents, use only DocumentListener
add required next JTextArea to the DocumentListener, if is there typed 4th. Char into JTextArea,
notice, moving with Focus from one JTextArea to another would be better wrapped into invokeLater
Replace text2.getCursor() with text2.requestFocus().
getCursor() is for retrieving the shape of the mouse pointer when hovering over a component.
Also, with this method it is still possible to enter more than 4 chars in a field, for example by pasting from clipboard. If you want to block that, you would need to check if text entered is longer than 4 chars, and if so, take only first 4 chars from it.
Something like this should work:
text1.addKeyListener(new KeyAdapter(){
public void keyPressed(KeyEvent e){
String value=text1.getText();
if(value.length()==4){
text2.requestFocus();
}
}
Where text2 is your next textfield
simply just create textarea and go to key typed events
den u may write this
String number=jTextArea1.getText();
int l=number.length();
if(l==3){
jTextArea1.transferFocus();
}