Dispose of SWT shell when cursor moves out of it - shell

I'm implementing a custom preview/tooltip for an Eclipse plug-in. It did it using a Shell in SWT, removing all its trimmings and placing a text box inside it. It looks great. However now I need to dispose of the shell when the cursor moves out of the shell window and I ran into some issues:
Does it make sense to attach a mousemoveListener to the shell? First I was doing this but then I realized that this listener only captures mouse move Events which occur inside the shell. How will I capture the mouse going out of the shell so as I could dispose of it?
Thanks and regards,
Krt_Malta

Attach a MouseTrackListener with MouseTrackAdapter as a listener and override mouseExit() method.

As another options, you can use AbstractHoverInformationControlManager from org.eclipse.jface.text, which handles all the nasty details (e.g. when you Alt+Tab out of the application, does your tooltip disappear?). The event handling is taken care of and you can focus on the interesting things. An example:
import org.eclipse.jface.text.AbstractHoverInformationControlManager;
import org.eclipse.jface.text.AbstractReusableInformationControlCreator;
import org.eclipse.jface.text.DefaultInformationControl;
import org.eclipse.jface.text.IInformationControl;
import org.eclipse.swt.events.DisposeEvent;
import org.eclipse.swt.events.DisposeListener;
import org.eclipse.swt.events.MouseEvent;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.layout.FillLayout;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Shell;
public class Example {
public static void main(String[] args) {
final Display display = new Display();
final Shell shell = new Shell(display);
shell.setLayout(new FillLayout());
shell.setSize(200, 200);
final ExampleHoverInformationControlManager controlManager = new ExampleHoverInformationControlManager();
// This must be done for the control for which you want the mouse
// tracking to be enabled and you possibly want to show hovers.
controlManager.install(shell);
shell.addDisposeListener(new DisposeListener() {
public void widgetDisposed(DisposeEvent e) {
controlManager.dispose();
}
});
shell.open();
while (!shell.isDisposed()) {
if (!display.readAndDispatch())
display.sleep();
}
display.dispose();
}
private static class ExampleHoverInformationControlCreator extends
AbstractReusableInformationControlCreator {
#Override
protected IInformationControl doCreateInformationControl(Shell parent) {
return new DefaultInformationControl(parent);
}
}
private static class ExampleHoverInformationControlManager extends
AbstractHoverInformationControlManager {
protected ExampleHoverInformationControlManager() {
super(new ExampleHoverInformationControlCreator());
}
#Override
protected void computeInformation() {
MouseEvent e = getHoverEvent();
// Just a static example area for simplicity
if (e.x >= 0 && e.x < 100 && e.y >= 0 && e.y < 20) {
Rectangle area = new Rectangle(0, 0, 100, 20);
setInformation(
"This can be a string or something else, you control it", area); //$NON-NLS-1$
return;
}
// computeInformation must setInformation in all cases
setInformation(null, null);
}
}
}

I just discovered a much better solution. It runs like this.
Shell coverup = new Shell(SWT.NO_TRIM) ;
coverup.setBounds(parentComposite.getShell().getBounds());
coverup.moveAbove( Display.getCurrent().getActiveShell() );
coverup.setAlpha( 13 );
coverup.setBackground(Display.getCurrent().getSystemColor(SWT.COLOR_LIST_BACKGROUND));
popupShell = new Shell( coverup, SWT.NO_TRIM );
coverup.addMouseMoveListener(new MouseMoveListener() {
#Override
public void mouseMove( MouseEvent mouseEvent )
{
System.out.println( "coverup - mouse moves" );
coverup.close() ;
}
});
Which in English is:
Create an invisible shell the same size as the application/parent shell.
Cover the parent shell with the invisible shell.
Attach a mouse Enter listener to the covering shell.
Create the actual popup as a child of, and on top of, the covering shell
When the mouse enters the popup, activate the coverup.
This means that no matter where the mouse goes after being in the popup, it enters the coverup shell - and the coverup mouse enter event disposes everything.
We are not detecting when we the mouse exits the popup - we are detecting when the mouse enters the surroundings.
Added bonus: setting the background of the coverup to a light gray, and a low Alpha, makes hte whole application subtly 'grayed-out' - so the user is aware is is disabled.
Small gotcha: if the popup shell is not wholly contained in the appwindow, where the popup shell goes outside the appwindow the mouse can escape without triggering the coverup shell.
Other than that - this works really well!

Related

Calling drawing commands externally in Processing

I am trying to use g4p-controls library in Processing to create a button that executes a drawing command in another window. In this library, a child window is created by the code
GWindow window = GWindow.getWindow(this, "Main", 100, 50, 500, 500, JAVA2D);
where this is the main applet, and the other arguments specify the name, position, and renderer.
GWindow is a subclass of PApplet, so I should feasibly be able to call a drawing command, for example window.background(0);, from anywhere in my code to paint that window black. However, this does not work, and I cannot figure out why. After all, the same code works when I put it in a handler function and add it to the window:
window.addDrawHandler(this, "windowDraw");
where the windowDraw method is
public void windowDraw(PApplet app, GWinData data) {
app.background(0);
}
Looking into the source code, the draw handler method windowDraw is invoked by the GWindow object with the first argument this, which is exactly the object referred to when I attempted window.background(0);. So window should be the object whose background() method is called to paint the canvas black.
If I am misunderstanding something fundamental about event-driven programming, please let me know. It does seem like the handlers take the relevant applet as an argument for a reason, but I really cannot see what's different about invocation inside and outside the handler functions.
Some additional notes: calling window.background(0); works if it's inside the main draw() function. It does not work if it's in the setup() function, and unfortunately for me, it doesn't work if it's in a button handler method:
public void handleButtonEvents(GButton button, GEvent event) {
if (this.button == button) {
if (event == GEvent.PRESSED) {
window.background(0);
}
}
}
Obviously, I have made sure this code actually runs when I press the button.
Even more strangely, if I substitute the above drawing call to window with something like window.strokeWeight(10), the change actually takes place, and subsequent lines in that canvas are drawn thicker. It only fails to actually draw things. I am just at a loss.
In the future, please try to post a MCVE instead of a bunch of disconnected code snippets. Here's an example:
import g4p_controls.*;
GWindow window;
void setup(){
window = GWindow.getWindow(this, "Main", 100, 50, 500, 500, JAVA2D);
}
void draw(){
background(255, 0, 0);
window.ellipse(mouseX, mouseY, 25, 25);
window.draw();
}
void mousePressed(){
window.background(0, 255, 0);
}
I would expect this code to draw circles in the second window, and to draw green in the second window when I press the mouse in the first window. However, it seems to only draw those things very sporadically.
In fact, here's the same type of program, in "pure Processing" without using the G4P library:
SecondApplet sa;
void setup() {
String[] args = {"TwoFrameTest"};
sa = new SecondApplet();
PApplet.runSketch(args, sa);
}
void settings() {
size(200, 200);
}
void draw() {
background(0);
sa.ellipse(mouseX, mouseY, 25, 25);
}
void mousePressed() {
sa.background(255, 0, 0);
}
public class SecondApplet extends PApplet {
public void settings() {
size(200, 200);
}
void draw() {
}
}
I would also expect this to work, but we see a similar gray window for the second sketch. I've filed a bug here to get feedback from the Processing devs about whether this is expected behavior.
In the meantime, you'll have better luck if you do something like this:
SecondApplet sa;
float drawMouseX;
float drawMouseY;
color drawBackground = #0000ff;
void setup() {
String[] args = {"TwoFrameTest"};
sa = new SecondApplet();
PApplet.runSketch(args, sa);
}
void settings() {
size(200, 200);
}
void draw() {
background(0);
drawMouseX = mouseX;
drawMouseY = mouseY;
}
void mousePressed() {
drawBackground = #00ff00;
}
public class SecondApplet extends PApplet {
public void settings() {
size(200, 200);
}
void draw() {
background(drawBackground);
ellipse(drawMouseX, drawMouseY, 25, 25);
}
}
Instead of calling the drawing functions directly, we're now setting some variables that are then used in the second applet's draw() function. There are a bunch of ways to do this, but the idea is the same: just don't call the draw functions directly, and call them from the draw() function instead.
Update: I heard back from Ben Fry (a founder of Processing) and codeanticode (a developer of Processing) on the bug I filed on GitHub, and I understand better now why this doesn't work.
The reason it doesn't work is because each sketch has its own UI thread, which is responsible for drawing and handling events. You can't draw to a sketch from a different thread, otherwise weird things happen. But you're trying to draw to a second sketch from the first sketche's event thread, which is not the second sketch's drawing thread, which is why it doesn't work.
See the bug for a discussion on alternative approaches, but honestly your best bet is probably to go with the approach I outlined of just sharing variables between the sketches.

Unity3D Draggable GUI.Box with GUI controls

I'm constructing a custom node editor window in Unity, and I've had a look at various resources such as this one, which uses GUI.Box to construct node windows.
This works great, and I'm able to drag these windows around the way I want, however once I add controls to the GUI.Box, I want them to override the Drag() function I've written.
Here's an example of the issue - When I move the vertical slider up, the entire box drags with it.
Is there a way to fix this behavior using GUI.Box, or should I go back to GUI.Window with its built-in GUI.DragWindow()?
Here's a simplified version of the code I'm using:
EditorMouseInput.cs:
private bool ActionLeftMouseDown()
{
mouseDownNode = editor.GetSelectedNode(Input.current.mousePosition);
if (mouseDownNode == null)
editor.StartMovingEditorCanvas();
else
mouseDownNode.IsSelected = true;
}
BaseNodeEditor.cs:
public BaseNode GetSelectedNode(Vector2 mousePos)
{
foreach (BaseNode node in Nodes)
{
if (node.WindowRect.Contains(mousePos))
return node;
}
return null;
}
public void Drag(Vector2 delta)
{
if (!MoveEditorMode && !ConnectionMode)
{
foreach (BaseNode node in Nodes)
{
node.Drag(delta);
}
}
BaseNode.cs:
public void Drag(Vector2 delta)
{
if (IsSelected)
draggedDistance += delta;
}
The vertical slider is added in the derived JumpNode class. Extract of the helper class that constructs the slider:
Vector2 pos = node.WindowRect.position + rect.position * GridSpacing;
value = GUI.VerticalSlider(new Rect(pos, rect.size * GridSpacing), value, maxValue, minValue);
I can see why this doesn't do what I want, but I don't know how to go about it given the GUI controls aren't part of the GUI.Box.
Any help or suggestions, even a nudge towards another source would be greatly appreciated - I feel I've used all the search terms that exist in my head!
Edit - Solved: Thanks to Kleber for solving this one for me. In case anyone else runs into this or a similar issue, the solution for me was in realising that GUI controls consume left mousedown events automatically, so clicking a slider means there's no propagation to the Box to check if it was clicked.
What I needed to do was separate the IsSelected and IsDragged flags in the Node class, and clear IsDragged on mouseUp. I originally used IsSelected to flag both drag enabled, and selected (multiple nodes could be selected and dragged at once).
It's quite a complex tutorial so I didn't read it entirely, but the problem seems to be the MouseDrag detection. Well, basically you want to stop the event propagation when you click on a GUI element inside the Box, right? To do so, you call:
Event.current.Use()
every time the user drags the mouse on one of your components.
Using the resource you've mentioned, I altered the Node class and added a slider inside the Draw() method, ending like this:
public void Draw() {
inPoint.Draw();
outPoint.Draw();
GUI.Box(rect, title, style);
GUI.BeginGroup(rect);
_value = GUI.HorizontalSlider(new Rect(20, 0, 50, 20), _value, 100, -100);
GUI.EndGroup();
}
Another thing you can do is change how you draw your window. Here it's a simple example that I've tested on the latest Unity version (5.6):
private void OnGUI() {
GUI.Box(_rect, string.Empty);
GUI.BeginGroup(_rect);
_value = GUI.VerticalSlider(new Rect(145, 100, 20, 100), _value, 100, -100);
GUI.EndGroup();
var e = Event.current;
if (e.type == EventType.MouseDrag && _rect.Contains(e.mousePosition)) {
_rect.x += e.delta.x;
_rect.y += e.delta.y;
Repaint();
}
}
As you can see, this example doesn't need an Event.current.Use() to work properly.

JavaFX Collection of Shapes

I am trying to create a JavaFX control that is essentially a bunch of interactable shapes. This is what I have so far:
import javafx.event.EventHandler;
import javafx.scene.Group;
import javafx.scene.input.MouseEvent;
import javafx.scene.input.ScrollEvent;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
public class ScenarioViewer extends Group {
// I want 1mm == 1px so scale everything so that 0.001 == 1px
private static final int DEFAULT_SCALE = 1000;
private static final int DEFAULT_SENSITIVITY = 100;
private double sensitivity;
private Rectangle testRect;
public ScenarioViewer() {
sensitivity = DEFAULT_SENSITIVITY;
testRect = new Rectangle(0.0, 0.0, 0.005, 0.01);
testRect.setStroke(Color.BLACK);
testRect.setFill(null);
testRect.setStrokeWidth(0.001);
getChildren().add(testRect);
setupScale();
setupEventHandlers();
}
private void scale(double change) {
setScaleX(getScaleX() + change);
setScaleY(getScaleY() + change);
}
private void translate(double x, double y) {
setTranslateX(getTranslateX() + x);
setTranslateY(getTranslateY() + y);
}
private void setupScale() {
setScaleX(DEFAULT_SCALE);
setScaleY(DEFAULT_SCALE);
}
private EventHandler<ScrollEvent> onScroll = new EventHandler<ScrollEvent>() {
public void handle(ScrollEvent event) {
scale(event.getDeltaY() * sensitivity);
}
};
private EventHandler<MouseEvent> onDrag = new EventHandler<MouseEvent>() {
public void handle(MouseEvent event) {
if(event.isPrimaryButtonDown()) {
translate(event.getX() * sensitivity,
event.getY() * sensitivity);
}
}
};
private void setupEventHandlers() {
addEventHandler(ScrollEvent.SCROLL, onScroll);
addEventHandler(MouseEvent.MOUSE_DRAGGED, onDrag);
}
}
The problem with the above is that the events are only triggered if the mouse is over the rectangle, more specifically, the edges of the rectangle as it is not filled. I want the opposite, such that, the events are only triggered when the mouse is not over a shape so that specific event handlers can be registered to each shape.
Why is this happening?
I believe it is because my control extends Group, and this relevant line in the documentation:
Any transform, effect, or state applied to a Group will be applied to
all children of that group. Such transforms and effects will NOT be
included in this Group's layout bounds, however if transforms and
effects are set directly on children of this Group, those will be
included in this Group's layout bounds.
Is there a more appropriate class to extend?
Update
I've changed the base class to Region, this makes events trigger properly but now the scaling and translations don't work like before. I have to instead iterate over everything and scale/translate each individual shape? Why does calling getScaleX/Y and setTranslateX/Y act differently between Region and Group?
On your group, call setPickOnBounds(true), this will allow the group to intercept any mouse/touch/input events within it's bounds.
specific event handlers can be registered to each shape.
Just invoke shape.setOnMouseClicked(eventHandler), etc (you can also use such setters on your enclosing group to handle events at the group level). You can consume the events in the event handler if you don't want them to bubble up to the enclosing group, similarly you can add filters to the parent group if you don't want the events to reach the children.
It may help if you review the Oracle tutorials on JavaFX event handling.

JavaFX 2.2: Hooking Slider Drag n Drop Events

I am trying to catch the events on the JavaFX Slider especially the one which indicates that the drag stopped and was released. At first I used the valueProperty with mock-up code like this
slider.valueProperty().addListener(new ChangeListener<Number>() {
#Override
public void changed(ObservableValue<? extends Number> ov, Number oldValue, Number newValue) {
log.fine(newValue.toString());
}
});
but with this it update too often. So I searched within SceneBuilder and the API and found some interessting like
slider.setOnMouseDragReleased(new EventHandler<MouseDragEvent>() {
#Override
public void handle(MouseDragEvent event) {
System.out.println("setOnMouseDragReleased");
}
});
but they never get fired. There only some like setOnMouseReleased I get some output, but this for example count for the whole Node like the labels etc.
So my question is, which is the correct hook to know the value is not changing anymore (if possible after release of the mouse like drag'n'drop gesture) and maybe with a small example to see its interfaces working.
Add a change listener to the slider's valueChangingProperty to know when the slider's value is changing, and take whatever action you want on the value change.
The sample below will log the slider's value when it starts to change and again when it finishes changing.
import javafx.application.Application;
import javafx.beans.value.*;
import javafx.geometry.*;
import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.scene.layout.*;
import javafx.stage.Stage;
public class SliderChangeLog extends Application {
private final ListView<String> startLog = new ListView<>();
private final ListView<String> endLog = new ListView<>();
#Override public void start(Stage stage) throws Exception {
Pane logsPane = createLogsPane();
Slider slider = createMonitoredSlider();
VBox layout = new VBox(10);
layout.setAlignment(Pos.CENTER);
layout.setPadding(new Insets(10));
layout.getChildren().setAll(
slider,
logsPane
);
VBox.setVgrow(logsPane, Priority.ALWAYS);
stage.setTitle("Slider Value Change Logger");
stage.setScene(new Scene(layout));
stage.show();
}
private Slider createMonitoredSlider() {
final Slider slider = new Slider(0, 1, 0.5);
slider.setMajorTickUnit(0.5);
slider.setMinorTickCount(0);
slider.setShowTickMarks(true);
slider.setShowTickLabels(true);
slider.setMinHeight(Slider.USE_PREF_SIZE);
slider.valueChangingProperty().addListener(new ChangeListener<Boolean>() {
#Override
public void changed(
ObservableValue<? extends Boolean> observableValue,
Boolean wasChanging,
Boolean changing) {
String valueString = String.format("%1$.3f", slider.getValue());
if (changing) {
startLog.getItems().add(
valueString
);
} else {
endLog.getItems().add(
valueString
);
}
}
});
return slider;
}
private HBox createLogsPane() {
HBox logs = new HBox(10);
logs.getChildren().addAll(
createLabeledLog("Start", startLog),
createLabeledLog("End", endLog)
);
return logs;
}
public Pane createLabeledLog(String logName, ListView<String> log) {
Label label = new Label(logName);
label.setLabelFor(log);
VBox logPane = new VBox(5);
logPane.getChildren().setAll(
label,
log
);
logPane.setAlignment(Pos.TOP_LEFT);
return logPane;
}
public static void main(String[] args) { launch(args); }
}
There could be times when you want to know when the user is moving the slider versus the slider value changing due to a binding to a property. One example is a slider that is used on a media player view to show the media timeline. The slider not only displays the time but also allows the user to fast forward or rewind. The slider is bound to the media player's current time which fires the change value on the slider. If the user moves the slider, you may want to detect the drag so as to stop the media player, have the media player seek to the new time and resume playing. Unfortunately the only drag event that seems to fire on the slider is the setOnDragDetected event. So I used the following two methods to check for a slider drag.
slider.setOnDragDetected(new EventHandler<Event>() {
#Override
public void handle(Event event) {
currentPlayer.pause();
isDragged=true;
}
});
slider.setOnMouseReleased(new EventHandler<Event>() {
#Override
public void handle(Event event) {
if(isDragged){
currentPlayer.seek(Duration.seconds((double) slider.getValue()));
currentPlayer.play();
isDragged=false;
}
}
});
jewelsea's answer was very helpful for setting me on the right track, however if "snapToTicks" is on, undesired behavior results. The "end" value as captured by jewelsea's listener is before the snap takes place, and the post-snap value is never captured.
My solution sets a listener on value but uses valueChanging as a sentinel. Something like:
slider.valueProperty().addListener(new ChangeListener<Number>() {
#Override
public void changed(
ObservableValue<? extends Number> observableValue,
Number previous,
Number now) {
if (!slider.isValueChanging()
|| now.doubleValue() == slider.getMax()
|| now.doubleValue() == slider.getMin()) {
// This only fires when we're done
// or when the slider is dragged to its max/min.
}
}
});
I found that checking for the max and min value was necessary to catch the corner case where the user drags the slider all the way past its left or right bounds before letting go of the mouse. For some reason, that doesn't fire an event like I'd expect, so this seems like an okay work-around.
Note: Unlike jewelsea, I'm ignoring the starting value for the sake of simplicity.
Note 2: I'm actually using ScalaFX 2, so I'm not sure if this Java translation compiles as-written.

How do I limit mouse pointer movement in wxWidgets?

Is there a way to limit mouse pointer movement to a specific area in wxWidgets? I know there is an API function ClipCursor() in Windows, but is there a method in wxWidgets for all platforms?
No. There is no such function in wx by all i know. Start up a timer (say 50ms) checking the global mouse position. If the mouse is outside the region, then set it into again.
If you want to restrict the mouse for some certain reason, for example to make some sort of game, then you can capture the mouse (see wxWindow::CaptureMouse). You will get mouse events even if the pointer is outside your window. Then you could react to mouse-motion events and do the check for the position there, without a timer. Downside of this is that the mouse won't be able to be used somewhere else for other programs since they won't receive events.
wxWidgets manual states that OSX guidelines forbid the programs to set the mouse pointer to a certain position programmatically. That might contribute to the reason there is not much support for such stuff in wx, especially since wx tries really hard to be compatible to everything possible.
Small sample. Click on the button to restrict the mouse to area 0,0,100,100. Click somewhere to release it.
#include <wx/wx.h>
namespace sample {
class MyWin : public wxFrame {
public:
MyWin()
:wxFrame(0, wxID_ANY, wxT("haha title")) {
mRestricted = wxRect(0, 0, 100, 100);
mLast = mRestricted.GetTopLeft();
wxButton * button = new wxButton(this, wxID_ANY, wxT("click this"));
}
private:
void OnClicked(wxCommandEvent& event) {
if(!HasCapture()) {
CaptureMouse();
CheckPosition();
}
}
void OnMotion(wxMouseEvent& event) {
CheckPosition();
}
void OnLeft(wxMouseEvent& event) {
if(HasCapture())
ReleaseMouse();
}
void CheckPosition() {
wxPoint pos = wxGetMousePosition();
if(!mRestricted.Contains(pos)) {
pos = ScreenToClient(mLast);
WarpPointer(pos.x, pos.y);
} else {
mLast = pos;
}
}
wxRect mRestricted;
wxPoint mLast;
DECLARE_EVENT_TABLE();
};
BEGIN_EVENT_TABLE(MyWin, wxFrame)
EVT_BUTTON(wxID_ANY, MyWin::OnClicked)
EVT_MOTION(MyWin::OnMotion)
EVT_LEFT_DOWN(MyWin::OnLeft)
END_EVENT_TABLE()
class MyApp : public wxApp {
virtual bool OnInit() {
MyWin * win = new MyWin;
win -> Show();
SetTopWindow(win);
return true;
}
};
} /* sample:: */
IMPLEMENT_APP(sample::MyApp)

Resources