I am searching for a way to create a touch application in Java (NOT for mobile devices) with a touch scroll support.
Ive been searching so far and I am investigating how this could be done - what ive found is the MT4J (http://www.mt4j.org/) but it seems that it would not support that (please correct me if i am wrong).
So my question is, how can i emulate a scroll event on a horizontal touch / swipe?
Thanks for help!
Kind Regards,
Alex
This implements a touch and drag scroller by sub-classing JScrollPane.
Because touch and drag is not enough on its own I have added momentum
so that it 'throws' the scroll when the mouse button is released.
There is no 'bounce' at the ends of the scroll as I cannot afford to
fight a lawsuit against the owners of 'bounce'.
It's not completely encapsulated because, although it works fine if
the view is a jlist, there may be components on the view that need
to modify their response if the panel was being dragged at the time.
Also some components, e.g. JRadioButton, JCheckBox, etc, consume
mouse clicks without passing them up to the container, so you will
need to add TouchScroll's MouseListener and MouseMotionListener to them.
import java.awt.Component;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import javax.swing.JScrollBar;
import javax.swing.JScrollPane;
public class TouchScroll extends JScrollPane implements MouseListener, MouseMotionListener {
private JScrollBar vbar = this.getVerticalScrollBar();
public TouchScroll(Component view){ // 1-arity CONSTRUCTOR
super(view);
view.addMouseListener(this);
view.addMouseMotionListener(this);
}
public TouchScroll() { super(); } // 0-arity CONSTRUCTOR
public void setViewportView(Component view) {
super.setViewportView(view);
view.addMouseListener(this);
view.addMouseMotionListener(this);
}
private static boolean wasdragged = false; // other MouseListeners may need to know this ...
public boolean wasDragged() { return wasdragged; } // ... this gives them safe access
static int lastY = 0, distY = 0;
double momentum = 0; // not really physical momentum but it will be used to 'throw' the scroll when the mouse button is released
static boolean lbdown = false;
#Override
public void mouseDragged(MouseEvent e) {
wasdragged = true;
distY = 0;
int currentY = e.getYOnScreen();
if(lbdown) {
distY = lastY - currentY;
vbar.setValue(distY + vbar.getValue());
if(Math.abs(distY) > 1)
momentum = distY + distY; // magnify and log the momentum for later use
}
lastY = currentY;
}
#Override
public void mousePressed(MouseEvent e) {
if (e.getButton() == MouseEvent.BUTTON1) {
lastY = e.getYOnScreen();
lbdown = true;
}
}
#Override
public void mouseReleased(MouseEvent e) {
if (e.getButton() == MouseEvent.BUTTON1)
lbdown = false;
if(wasdragged)
wasdragged = false;
if(Math.abs(momentum) <= 4.0) // then assume that the mouse wasn't moving (much) when the button was released
return;
// otherwise 'throw' the scroll
int max = vbar.getMaximum();
int count;
double brakingforce = 1.04; // set an initial braking force
for(count = 1000; count > 0; count--){ // don't allow it to accidentally go on forever
momentum = momentum / brakingforce; // apply the brake
if(Math.abs(momentum) < 1.5)
brakingforce = 1.02; // ease off the brake towards the end (gives a slight overrun ala iOS)
if(Math.abs(momentum) < 1.0) // bring it to a halt
break;
int val = vbar.getValue();
if(val < 0 || val > max) // prevent overrun
break;
vbar.setValue((int) momentum + val); // increment the scroll bar
try {
Thread.sleep(10); // slow the loop down so the user can see the scroll
} catch (InterruptedException e1) {
e1.printStackTrace();
}
}
}
public void mouseMoved(MouseEvent e) {}
public void mouseClicked(MouseEvent e) {}
public void mouseEntered(MouseEvent e) {}
public void mouseExited(MouseEvent e) {}
}
Here is a 'bare bones' example of how you might apply it:
panel = new JPanel();
jlist = new JList(list);
scroller = new TouchScroll(jlist);
panel.add(scroller);
Any components that respond to a mouse release may need to do something like this:
public void mouseReleased(MouseEvent e) {
if(scroller.wasDragged())
return;
actionENTER();
}
Lastly, as mentioned earlier, components that consume mouse events will need
to inform the scroller like this:
JCheckBox checkbox = new JCheckBox(text);
checkbox.addMouseMotionListener(scroller);
checkbox.addMouseListener(scroller);
You can use JavaFX's touch event framework.
Here is some sample code from the linked tutorial on handling scroll events:
rect.setOnScroll(new EventHandler<ScrollEvent>() {
#Override public void handle(ScrollEvent event) {
if (!event.isInertia()) {
rect.setTranslateX(rect.getTranslateX() + event.getDeltaX());
rect.setTranslateY(rect.getTranslateY() + event.getDeltaY());
}
log("Rectangle: Scroll event" +
", inertia: " + event.isInertia() +
", direct: " + event.isDirect());
event.consume();
}
});
rect.setOnScrollStarted(new EventHandler<ScrollEvent>() {
#Override public void handle(ScrollEvent event) {
inc(rect);
log("Rectangle: Scroll started event");
event.consume();
}
});
rect.setOnScrollFinished(new EventHandler<ScrollEvent>() {
#Override public void handle(ScrollEvent event) {
dec(rect);
log("Rectangle: Scroll finished event");
event.consume();
}
});
Related
I have an array of JToggleButtons that make a calendar selector of sorts. I have implemented a way to drag the mouse over the buttons to toggle multiple days without having to stop and click each one, and it works great for slower mouse movements:
JToggleButton[] buttons = getCalendarDayButtonArray(); //arbitrary instantiation
for (int d = 0; d < 31; d++) {
final JToggleButton b = new JToggleButton(day);
buttons[d] = b;
buttons[d].addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {//do stuff...}
}
buttons[d].addMouseListener(new MouseAdapter() {
public void mouseEntered(MouseEvent e) {
if (e.getModifiers() == MouseEvent.BUTTON1_MASK) {
b.doClick();
}
}
public void mousePressed(MouseEvent e) {
if (e.getModifiers() == MouseEvent.BUTTON1_MASK) {
b.doClick();
}
}
});
}
However, this doesn't work entirely for quick movements. I am not sure whether it is a problem with the polling rate of the mouse, or a result of any lag on the computer itself, but it seems that the mouse skips over some buttons entirely and as a result the mouseEntered method is not invoked for these buttons. Is there a workaround that doesn't involve the user simply moving the mouse slowly? Thanks in advance!
Based on previous stack questions I have written the following code to capture mouse input from the glass pane push it to a series of panels underneath.
MouseMotionListener mml = new MouseMotionListener() {
private void DispatchMouseEvent(MouseEvent e) {
Point glassPanePoint = e.getPoint();
Container container = jf.getContentPane();
Point containerPoint = SwingUtilities.convertPoint(
glass, glassPanePoint, container);
if (containerPoint.y >= 0) {
for (Component ml : mouseListeners) {
Point componentPoint = SwingUtilities
.convertPoint( glass, glassPanePoint, ml);
ml.dispatchEvent(new MouseEvent(ml, e.getID(),
e.getWhen(), e.getModifiers(),
componentPoint.x, componentPoint.y,
e.getClickCount(), e.isPopupTrigger()));
}
}
}
#Override
public void mouseDragged(MouseEvent me) {
DispatchMouseEvent(me);
}
#Override
public void mouseMoved(MouseEvent me) {
DispatchMouseEvent(me);
}
};
glass.addMouseMotionListener(mml);
But all of the tutorials I can find say something like this when it comes to the menu bar.
if (containerPoint.y < 0) { //we're not in the content pane
if (containerPoint.y + menuBar.getHeight() >= 0) {
//The mouse event is over the menu bar.
//Could handle specially.
} else {
//The mouse event is over non-system window
//decorations, such as the ones provided by
//the Java look and feel.
//Could handle specially.
}
Could someone point me to somewhere so I can see how to handle that specifically? Thanks.
I'm trying to catch the event when the small "minimize" or "maximize" arrows of a JSplitPane's divider are clicked.
I found a way to listen to a click elsewhere on the divider bar. Something like :
SplitPaneUI spui = splitPane.getUI();
if (spui instanceof BasicSplitPaneUI) {
((BasicSplitPaneUI) spui).getDivider().addMouseListener(new MouseAdapter() {
public void mouseClicked(MouseEvent e) {
// do something...
}
});
}
But is there a way to listen to clicks on the arrows?
I'm trying to catch the event when the small "minimize" or "maximize" arrows of a JSplitPane's divider are clicked.
Maybe you could add a PropertyChangeListener to the JSPlitPane and listen for dividerLocation changes (assuming you don't care if the divider was dragged or "one clicked" to the start or end). Then you can check if the divider location is at 0 or the maximum.
splitPane.addPropertyChangeListener("dividerLocation", this);
...
public void propertyChange(PropertyChangeEvent e)
{
// Get the new divider location of the split pane
int location = ((Integer)e.getNewValue()).intValue();
if (location == 0)
// do something
else if (location == splitPane.getMaximumDividerLocation())
// do something else
}
If you do care about the difference between dragging and clicking, then maybe you can compare the old/new values and look for a change in the location greater than a specified value.
Another option is to get the button from the divider:
JSplitPane splitPane = (JSplitPane)e.getSource();
BasicSplitPaneUI ui = (BasicSplitPaneUI)splitPane.getUI();
BasicSplitPaneDivider divider = ui.getDivider();
JButton button1 = (JButton)divider.getComponent(0)
Now you can add another ActionListener to the button to do your custom code.
Reflection is the Only way I guess to listen to One Touch Expandable Buttons
Just pass addSplitPaneListener and it is done.
public void addSplitPaneListener(JSplitPane splitPane) {
addSplitPaneListener("leftButton", splitPane);
addSplitPaneListener("rightButton", splitPane);
}
public void addSplitPaneListener(String button, JSplitPane splitPane) {
try {
Field field = BasicSplitPaneDivider.class.getDeclaredField(button);
field.setAccessible(true);
JButton onetouchButton = (JButton) field.get(((BasicSplitPaneUI) splitPane.getUI()).getDivider());
onetouchButton.setActionCommand(button);
onetouchButton.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
buttonClicked(e);
}
});
} catch (NoSuchFieldException | SecurityException | IllegalArgumentException | IllegalAccessException ex) {
//Logger.getLogger(NewJFrame1.class.getName()).log(Level.SEVERE, null, ex);
}
}
public void buttonClicked(ActionEvent e) {
System.out.println("Clicked " + e.getActionCommand());
}
Here is a derived class that deals with expanding/hiding either using the one touch expander and/or an additional keylistener bound to a certain key to hide/unhide the RIGHT panel. Doing it for the left should easy to adapt ;)
Interestingly on Linux the right part will never get its theoretical maximum value but will be one less (BUGCLICKONETOUCH). Didn't test if this is also true on Windows.
import java.awt.Component;
import javax.swing.JSplitPane;
public class JToggleSplitPane extends JSplitPane {
private int restoreWidth = -1;
private final static int BUGCLICKONETOUCH = 1;
public JToggleSplitPane(int splittype, Component c1, Component c2) {
super(splittype, c1, c2);
setOneTouchExpandable(true);
}
public void toggle() {
try {
if (getDividerLocation() < getWidth() - getDividerSize() - BUGCLICKONETOUCH) {
restoreWidth = getWidth() - getDividerSize() - getDividerLocation();
setDividerLocation(1.0d);
} else {
setDividerLocation(getWidth() - getDividerSize() - restoreWidth);
}
updateUI();
doLayout();
} catch (Exception e) {
e.printStackTrace();
}
}
public void doLayout() {
super.doLayout();
if (restoreWidth == -1) {
restoreWidth = rightComponent.getWidth();
}
}
}
I have a swing component that has several sub components. What I want to do change some label if the mouse is over any of those components, and then change it to something else if the mouse moves off all of the components. I'm trying to find a more efficient way to do this.
Currently I have mouse listeners over all of the child components that look something like:
class AMouseListener extends MouseAdapter {
private boolean mouseOver;
mouseEntered(MouseEvent e) { mouseOver = true; updateLabel(); }
mouseExited(MouseEvent e) { mouseOver = false; updateLabel(); }
void updateLabel() {
String text = "not-over-any-components";
// listeners are each of the listeners added to the child components
for ( AMouseListener listener :listeners ) {
if ( listener.mouseOver ) {
text = "over-a-component";
break;
}
}
}
}
This works, but I feel like there should be a better way to handle this by only handling mouseEntered and mouseExited events on the parent container, but since the child components intercept these events, I'm not sure how to go about doing this (I don't necessarily have control over the child components so I Can't forward the mouse events to the parent event if I wanted to).
For example
import java.awt.Component;
import java.awt.Dimension;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import javax.swing.*;
public class TestMouseListener {
public static void main(String[] args) {
final JComboBox combo = new JComboBox();
combo.setEditable(true);
for (int i = 0; i < 10; i++) {
combo.addItem(i);
}
final JLabel tip = new JLabel();
tip.setPreferredSize(new Dimension(300, 20));
JPanel panel = new JPanel();
panel.add(combo);
panel.add(tip);
JFrame frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
frame.add(panel);
frame.pack();
frame.setVisible(true);
panel.addMouseListener(new MouseAdapter() {
#Override
public void mouseEntered(MouseEvent e) {
tip.setText("Outside combobox");
}
#Override
public void mouseExited(MouseEvent e) {
Component c = SwingUtilities.getDeepestComponentAt(
e.getComponent(), e.getX(), e.getY());
// doesn't work if you move your mouse into the combobox popup
tip.setText(c != null && SwingUtilities.isDescendingFrom(
c, combo) ? "Inside combo box" : "Outside combobox");
}
});
}
private TestMouseListener() {
}
}
Check out the docs and examples for the "glass pane".
This should give you what you need: The Glass Pane
I know this is very old, but here's a simple solution with which you can create a mouse listener for a component and all components inside it's bounds (without adding the listener to all components individually):
/**
* Creates an {#link AWTEventListener} that will call the given listener if
* the {#link MouseEvent} occurred inside the given component, one of its
* children or the children's children etc. (recursive).
*
* #param component
* the component the {#link MouseEvent} has to occur inside
* #param listener
* the listener to be called if that is the case
*/
public static void addRecursiveMouseListener(final Component component, final MouseListener listener) {
Toolkit.getDefaultToolkit().addAWTEventListener(new AWTEventListener() {
#Override
public void eventDispatched(AWTEvent event) {
if(event instanceof MouseEvent) {
MouseEvent mouseEvent = (MouseEvent) event;
if(mouseEvent.getComponent().isShowing() && component.isShowing()){
if (containsScreenLocation(component, mouseEvent.getLocationOnScreen())) {
if(event.getID() == MouseEvent.MOUSE_PRESSED) {
listener.mousePressed(mouseEvent);
}
if(event.getID() == MouseEvent.MOUSE_RELEASED) {
listener.mouseReleased(mouseEvent);
}
if(event.getID() == MouseEvent.MOUSE_ENTERED) {
listener.mouseEntered(mouseEvent);
}
if(event.getID() == MouseEvent.MOUSE_EXITED) {
listener.mouseExited(mouseEvent);
}
if(event.getID() == MouseEvent.MOUSE_CLICKED){
listener.mouseClicked(mouseEvent);
}
}
}
}
}
}, AWTEvent.MOUSE_EVENT_MASK);
}
/**
* Checks if the given location (relative to the screen) is inside the given component
* #param component the component to check with
* #param screenLocation the location, relative to the screen
* #return true if it is inside the component, false otherwise
*/
public static boolean containsScreenLocation(Component component, Point screenLocation){
Point compLocation = component.getLocationOnScreen();
Dimension compSize = component.getSize();
int relativeX = screenLocation.x - compLocation.x;
int relativeY = screenLocation.y - compLocation.y;
return (relativeX >= 0 && relativeX < compSize.width && relativeY >= 0 && relativeY < compSize.height);
}
Note: Once the mouse exits the root component of this listener the mouseExited(mouseEvent) will probably not fire, however you can just add the mouse listener to the root component itself and it should fire.
mouseExited(mouseEvent) is unreliable in general though.
You could initiate a single instance of the listener and add that instance to each component.
Like this:
AMouseListener aMouseListener=new AMouseListener();
for each(Component c:components) {
caddMouseListener(aMouseListener);
}
I wondering how to change the size of the layout associated to a VisualizationViewer with jung?
I explain the purpose: actually, when using a SatelliteVisualizationViewer, we can pick a node and move it in an area that is not covered by the SatelliteVisualisationViewer.
We can see this in the SatelliteViewDemo class in the JUNG distribution. Launch the demo, pick a node and move it outside the layout size. The node is well displayed in the VisualizationViewer but not in the satellite view.
So, I try to add some code in the MouseListener of PickingGraphMousePlugin, specifically in the mouseReleased(...) method in order to update the layout size by the size of the rectangle that contains all the nodes of the graph. Doing this, the SatelliteView should be updated.
But how to do this?
Edit: Sorry to have not detailled enough. In fact, changing the layout size does not change the SatelliteView, so vertex located outside the layout (after a pick and move operation) are still not covered by the SatelliteView.
Maybe I have miss something...
Actually the real question is: how to change the area covered by the SatelliteVisualizationViewer? !!!
It sounds like you've got a pretty good idea of how to proceed. What piece are you stuck on? Changing the layout size? Updating the visualization? Something else?
I wanted to automatically update the satellite view relatively to a VisualizationViewer with picking support enabled.
I have finally adopted another solution: no automatic update of the satellite view but a pan using mouse button 2, and a zoom using CTRL+Mouse_Wheel.
Here is the class, it may be helpful... :
public class SatelliteVisualizationViewerMouseControler implements
MouseListener, MouseMotionListener, MouseWheelListener {
private SatelliteVisualizationViewer<SVertex, PEdge> vv;
private Point drag_point;
private double zoom_factor = 0.1;
public SatelliteVisualizationViewerMouseControler(
SatelliteVisualizationViewer<SVertex, PEdge> vv) {
this.vv = vv;
}
#Override
public void mouseDragged(MouseEvent e) {
if (drag_point != null) {
Point2D l_drag_point = vv.getRenderContext()
.getMultiLayerTransformer()
.inverseTransform(Layer.LAYOUT, drag_point);
Point2D l_e_point = vv.getRenderContext()
.getMultiLayerTransformer()
.inverseTransform(Layer.LAYOUT, e.getPoint());
double delta_x = l_e_point.getX() - l_drag_point.getX();
double delta_y = l_e_point.getY() - l_drag_point.getY();
double scale = vv.getRenderContext().getMultiLayerTransformer()
.getTransformer(Layer.VIEW).getScale();
vv.getRenderContext().getMultiLayerTransformer()
.getTransformer(Layer.LAYOUT)
.translate(delta_x * (1 / scale), delta_y * (1 / scale));
drag_point = e.getPoint();
}
}
#Override
public void mouseMoved(MouseEvent e) {
}
#Override
public void mouseClicked(MouseEvent e) {
}
#Override
public void mouseEntered(MouseEvent e) {
}
#Override
public void mouseExited(MouseEvent e) {
}
#Override
public void mousePressed(MouseEvent e) {
if (e.getButton() == MouseEvent.BUTTON2) {
drag_point = e.getPoint();
}
}
#Override
public void mouseReleased(MouseEvent e) {
if (e.getButton() == MouseEvent.BUTTON2) {
drag_point = null;
}
}
#Override
public void mouseWheelMoved(MouseWheelEvent e) {
if (e.getModifiersEx() == MouseEvent.CTRL_DOWN_MASK) {
vv.getRenderContext()
.getMultiLayerTransformer()
.getTransformer(Layer.LAYOUT)
.scale(1 + (e.getWheelRotation() * zoom_factor),
1 + (e.getWheelRotation() * zoom_factor),
vv.getCenter());
}
}
}