I have a JTextArea. I have a function that selects some amount of text when some combination is called. It's done properly. The thing is, I want to move caret to the selection beginning when some text is selected and VK_LEFT is pressed. KeyListener is implemented properly, I tested it in other way. The thing is, that when I write following code:
#Override public void keyPressed( KeyEvent e) {
if(e.getKeyChar()==KeyEvent.VK_LEFT)
if(mainarea.getSelectedText()!=null)
mainarea.setCaretPosition(mainarea.getSelectionStart());
}
and add an instance of this listener to mainarea, select some text (using my function) and press left arrow key, the caret position is set to the end of selection... And I wont it to be in the beginning... What's the matter? :S
Here's a code snippet
Action moveToSelectionStart = new AbstractAction("moveCaret") {
#Override
public void actionPerformed(ActionEvent e) {
int selectionStart = textComponent.getSelectionStart();
int selectionEnd = textComponent.getSelectionEnd();
if (selectionStart != selectionEnd) {
textComponent.setCaretPosition(selectionEnd);
textComponent.moveCaretPosition(selectionStart);
}
}
public boolean isEnabled() {
return textComponent.getSelectedText() != null;
}
};
Object actionMapKey = "caret-to-start";
textComponent.getInputMap().put(KeyStroke.getKeyStroke("LEFT"), actionMapKey);
textComponent.getActionMap().put(actionMapKey, moveToSelectionStart);
Note: it's not recommended to re-define typically installed keybindings like f.i. any of the arrow keys, users might get really annoyed ;-) Better look for some that's not already bound.
Related
Not sure how well I will explain this; I'm quite new to programming...
So I'm trying to make a desktop application that draws musical notes of a given type on some sheet music when the user selects the button corresponding to that type of note. Currently, if the user selects the "Whole Note" button, the user can then start clicking on the screen and the note will be drawn where the click occurred. It will also make a "ding" sound and write some info about that note to a text file.
That's all well and good, but unfortunately when the user selects a new button, say the "Quarter Note" button, for each mouse click there will be two notes drawn (one whole, one quarter), two dings, and two packets of info written to the file. I have no idea how to make this work! Currently, I'm trying to use threads, such that each button creates a new thread and the thread currently in use is interrupted when a new button is pressed, but that doesn't resolve the issue.
Initially, an empty linked list of threads ("NoteThreads") is constructed. Also, a private class known as SheetMusicPane (given the variable name "smp") is constructed in order to draw the sheet music. The buttons are added in the main constructor (public CompositionStudio), whereas the method containing the mouse listener (see what follows) is contained in the SheetMusicPane private class. Not sure whether that is part of the problem.
I have a button action listener:
button.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
if (!threads.isEmpty()) {
NoteThread oldThread = threads.remove();
oldThread.interrupt();
}
NoteThread newThread = new NoteThread(e.getActionCommand());
threads.add(newThread);
newThread.run();
}
});
that produces a thread:
private class NoteThread extends Thread {
private String note;
public NoteThread(String note) {
this.note = note;
}
public void run() {
smp.getShape(smp.getGraphics(), note);
}
}
that when, on running, calls this method with graphics and a mouse listener:
public void getShape(final Graphics g, final String note) {
this.addMouseListener(new MouseListener() {
public void mousePressed(MouseEvent e) {
}
public void mouseReleased(MouseEvent e) {
}
public void mouseEntered(MouseEvent e) {
}
public void mouseExited(MouseEvent e) {
}
public void mouseClicked(MouseEvent e) {
Point p = MouseInfo.getPointerInfo().getLocation();
addShape(g, p.x, p.y, note);
int pitch = 12;
piano.playNote(pitch);
advance(1.0, piano);
try { addToFile(pitch, note);}
catch(FileNotFoundException fnfe) {}
catch(IOException ioe) {}
}
});
}
The above method is responsible for drawing the note ("addShape()"), making the ding sound, and writing to the file.
Thanks in advance for any help you can give!
what you're trying to do does not require multithreading. This is the approach that I'd take:
set up a set of toggle buttons or radio buttons to select the note to paint. this way, only one note will be selected at a time. add action listeners to those that store in an adequately scoped variable what note is selected, or infer that every time a note should be drawn. this way, you don't even add action listeners to the buttons. in any case, don't spawn new threads.
in your mouse listener, find out what note to draw, and do that - only one note.
if you can, stay away from multithreading, especially as a beginner. also, I think you confuse adding and running listeners here. each call to getShape() adds a new listener, meaning they accumulate over time, which might be the cause of your problems.
PS: welcome to stackoverflow! your question contained the important information and I could infer that you tried solving the problem yourself. It's pleasant to answer such questions!
One solution would be to simply fetch all the listeners (which should be 1) and remove them before adding the new listener:
public void getShape(final Graphics g, final String note) {
MouseListener[] listeners = this.getMouseListeners();
for (MouseListener ml : listeners) {
this.removeMouseListener(ml);
}
this.addMouseListener(new MouseListener()...);
}
An alternative, since you have a finite number of buttons, would be to create a finite set of listeners, eg:
private MouseListener wholeNote = new MouseListener()...;
private MouseListener quarterNote = new MouseListener()...;
Create a reference to the "current" listener (private MouseListener current;), and have a means of deciding which listener to use whenever getShape is called (a series of if conditions on the note String would work, although I would prefer some refactoring to use an enum personally). Then you could do something along the lines of:
private MouseListener wholeNote = new MouseListener()...;
private MouseListener quarterNote = new MouseListener()...;
private MouseListener current;
...
public void getShape(final Graphics g, final String note) {
if (current != null) {
this.removeMouseListener(current);
}
if (...) { // note is Whole Note
current = wholeNote;
} else if (...) { // note is Quarter Note
current = quarterNote;
} // etc.
this.addMouseListener(current);
}
Another alternative would be to change your listener so that you only ever need the one, but clicking a button changes a variable which the listener has access to. For example:
// In the listener
public void mouseClicked(MouseEvent e) {
Point p = MouseInfo.getPointerInfo().getLocation();
addShape(g, p.x, p.y, currentNote);
int pitch = 12;
piano.playNote(pitch);
advance(1.0, piano);
try { addToFile(pitch, currentNote);}
catch(FileNotFoundException fnfe) {}
catch(IOException ioe) {}
}
// In the same class
protected String currentNote;
...
public void getShape(final Graphics g, final String note) {
currentNote = note;
}
I have a JSpinner on which I would like to take control of when editing is enabled. It's easy enough with the keyboard, but how about those little arrow widgets at the side? I can't even find references to them in the JSpinner source or any of its enclosed classes.
You can use setUI() method to hide Jspinner arrow.
public void hideSpinnerArrow(JSpinner spinner) {
Dimension d = spinner.getPreferredSize();
d.width = 30;
spinner.setUI(new BasicSpinnerUI() {
protected Component createNextButton() {
return null;
}
protected Component createPreviousButton() {
return null;
}
});
spinner.setPreferredSize(d);
}
As you see, just make createNextButton() and createPreviousButton() return null.
Because we used a BasicUI so we have to setup Spinner size again. I used PreferredSize.
you can't take control of these two arrow buttons but you can do like this
private void buttonActionPerformed(java.awt.event.ActionEvent evt) {
p.setEnabled(false);
}
suppose you want a button pressed and the user will not able to use jspinner at all this is a hint for more actions you can modified it as well
you can also allow use to use the jspinner untill for a specific value using
if(spinner.getValue()==10){
//show error message and
spinner.setEnabled(false);
}
If the UI class used derives from BasicSpinnerUI, the arrow buttons can be removed with:
for (Component component : spinner.getComponents()) {
if (component.getName() != null && component.getName().endsWith("Button")) {
spinner.remove(component);
}
}
I have three different jtable on three diffenernt jscrollpane - one next to the other.
I successfuly written some code that makes them all scroll together when I scroll the mouse wheel.
I inherrited JScrollpane and overriden its processMouseWheelEvent methos as follows:
public void processMouseWheelEvent(MouseWheelEvent e) {
...
if (e.getSource() == this) {
for (BTSJScrollPane other : effected) {
other.processMouseWheelEvent(e);
}
}
}
I have canelled the pageup pagedown using
final InputMap inputMap =
comp.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);
inputMap.put(KeyStroke.getKeyStroke("PAGE_DOWN"), EMPTY);
inputMap.put(KeyStroke.getKeyStroke("PAGE_UP"), EMPTY);
comp.getActionMap().put(EMPTY, emptyAction);
But,
my only problem now is that when the user clicks up and down, they dont go together but rather scroll indipently.
Any suggestions ?
So far I wrote in my scrollpane something like
KeyboardFocusManager.getCurrentKeyboardFocusManager().addKeyEventDispatcher(new KeyEventDispatcher() {
#Override public boolean dispatchKeyEvent(KeyEvent e) {
if (thisIsThesourceTable) {
if (e.getKeyCode() == 38 || e.getKeyCode() == 40) {
}
}
return false;
}
});
But :
1. is there a better way?
2. maybe I should do it somehow in the actionMap?
3. how do I pass the key up/down event to the other scrollpane?
4. can I know if I am near the end of the viewing part in the scroll pane? is it needed
1, 2, & 3: You should use listeners on the scrollbars so it works with mouse, keyboard, and scrollbar interaction. That way you won't need key or mouse listeners. Here is one way:
scroll1.getVerticalScrollBar().addAdjustmentListener(new AdjustmentListener() {
#Override
public void adjustmentValueChanged(AdjustmentEvent e) {
scroll2.getVerticalScrollBar().setValue(e.getValue());
}
});
4: Look at JScrollPane's getVerticalScrollBar().getMaximumSize() and getModel() to find out if you're near the end.
I have a JComboBox component in the panel and ItemListener attached to it. But it gets fired after every up/down keypress (when scrolling though opened popup list). I want to change the selected value after the user accepts selection by pressing for example Enter key.
This is not a case when using mouse. When I move mouse over the combobox's list the highlight follows mouse pointer, but selected item is not changed until I press the mouse button. I would like to have the same behavior for keyboard, i.e. moving highlight via up/down arrow does not change selected item, but pressing Enter does.
I believe you should be able to do:
comboBox.putClientProperty("JComboBox.isTableCellEditor", Boolean.TRUE);
after you have created your comboBox instance to get this functionality
In Java 8 they have fixed this behaviour, but only trigger if u set one UI property
UIManager.getLookAndFeelDefaults().put("ComboBox.noActionOnKeyNavigation", true);
the JComboBox.isTableCellEditor method works for arrow movement through the list, but does not work for type-ahead supported by the KeySelectionManager. i.e. you still get ActionEvents for every non-navigation key the user types, as the JComboBox interprets those characters for searching though the model to move to (or move close to) the user's intended selection.
this solution has a drawback in that it changes the action command for mouse clicks, which was a OK compromise for me because the the flow of the GUI forces the user to change the focus away from the combo box
I ended up making a special KeyListener, that relys on changing the combo box's default action command from comboBoxChanged to comboBoxMovement. Here's the line of code I need after my combo box is all initialized:
setExplicitSelectionManager(myComboBox);
... and here is the method and its contained class that do all the work:
private void setExplicitSelectionManager(JComboBox comboBox) {
class ExplicitSelectionManager implements KeyListener, FocusListener {
private JComboBox src;
private KeyListener superKeyListener;
ExplicitSelectionManager(JComboBox src) {
this.src = src;
// we like what the default key listener does, but not the action command
// it uses for ActionEvents it fires for plain text type-ahead characters
this.superKeyListener = src.getKeyListeners()[0]; // we only have one
src.removeKeyListener(superKeyListener); // will be replace right away, below
}
#Override
public void keyTyped(KeyEvent e) {
// basic combo box has no code in keyTyped
}
#Override
public void keyPressed(KeyEvent e) {
// in the default JComboBox implementation, the KeySelectionManager is
// called from keyPressed. I'm fine with the implementation of
// the default, but I don't want it firing ActionEvents that will cause
// model updates
src.setActionCommand("comboBoxMovement");
this.superKeyListener.keyPressed(e);
src.setActionCommand("comboBoxChanged");
if (e.getKeyCode() == 10) {
src.setSelectedIndex(src.getSelectedIndex());
}
}
#Override
public void keyReleased(KeyEvent e) {
// basic combo box has no code in keyReleased
}
#Override
public void focusGained(FocusEvent e) {
}
#Override
// this will also give us the event we want, if the user decides to Tab out of
// the combo box, instead of hitting Enter
public void focusLost(FocusEvent e) {
src.setSelectedIndex(src.getSelectedIndex());
}
}
ExplicitSelectionManager newSelectionManager = new ExplicitSelectionManager(comboBox);
comboBox.addKeyListener(newSelectionManager);
comboBox.addFocusListener(newSelectionManager);
}
... and here's the action performed method
private void comboBoxActionPerformed(java.awt.event.ActionEvent evt) {
JComboBox source = (JComboBox) evt.getSource();
// "comboBoxChanged" is the default,
// so any normal JComboBox can also use this action listener
if (evt.getActionCommand().equals("comboBoxChanged")) {
updateModel(source.getName(), (String) source.getSelectedItem());
}
}
Its the expected behavior with the ItemListener. whenever the displayed value changes the event is fired. For your requirement use an ActionListener.
Is there a way to distinguish between a JComboBox's index being changed programatically using setSelectedBoundValue and by clicking on the JComboBox?
This is ugly and truly a hack, but works!
The ActionEvent contains a field modifiers which in this case is the mouse button id. So using that may help you distinguish between setSelectedIndex or setSelectedValue and mouse clicks (by the way setSelectedBoundValue is not a method on JComboBox):
box.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
if (e.getModifiers() != 0) {
// ~ mouse button pressed ;)
}
}
});
setSelectedBoundValue
Never heard of that method?
Is there a way to distinguish between a JComboBox's index being changed programatically
Not really. You can remove the listener:
comboBox.removeActionListener(...);
comboBox.setSelectedItem(...);
comboBox.addActionListener(...);
You can set your own class variable.
manualSelection = true;
comboBox.setSelectedIndex(...);
manualSelection = false;