Bug 19430 - Motif doesn't implement Control.setCapture() properly [portability]
Summary: Motif doesn't implement Control.setCapture() properly [portability]
Status: RESOLVED WONTFIX
Alias: None
Product: Platform
Classification: Eclipse Project
Component: SWT (show other bugs)
Version: 2.0   Edit
Hardware: PC Linux-Motif
: P3 major (vote)
Target Milestone: ---   Edit
Assignee: Grant Gayed CLA
QA Contact:
URL:
Whiteboard:
Keywords:
: 17509 (view as bug list)
Depends on:
Blocks:
 
Reported: 2002-06-05 17:24 EDT by Randy Hudson CLA
Modified: 2008-04-13 09:38 EDT (History)
2 users (show)

See Also:


Attachments

Note You need to log in before you can comment on or make changes to this bug.
Description Randy Hudson CLA 2002-06-05 17:24:08 EDT
On Motif, a Control will continue to get previously queued MouseMoves after a 
different control has requested mouse capture.  This doesn't happen on 
Windows.  We are doing something similar to what a Windows Tree does when the 
TreeItem is clipped.  We popup a Shell(NO_TRIM) with the un-clipped version.

In the attached example, you can mouse very quickly across the two Canvases 
that are used to popup a Shell.  When a canvas pops up a shell, it sets mouse 
capture on that shell because you aren't guaranteed that the next mouse event 
will be inside the popup shell.  But, even after setting capture on that popup 
shell it appears that the adjacent Canvas is also able to get a MouseEntered, 
causing a second popup shell to appear.  You will see the popup count go to 2 
at the console.  Since the second popup steals mouse capture, the first popup 
never knows when to go away.

import org.eclipse.swt.SWT;
import org.eclipse.swt.events.MouseEvent;
import org.eclipse.swt.events.MouseMoveListener;
import org.eclipse.swt.events.MouseTrackAdapter;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.layout.FillLayout;
import org.eclipse.swt.widgets.Canvas;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Shell;

public class PopupTestOld {

static int captureCount;

public static void main(String[] args) {
	new PopupTestOld().run();
}

public void run() {
	Shell shell = new Shell();
	shell.setSize(300, 200);
	shell.setText("Popup Test");
//	shell.setLayout(layout);
	
	final Canvas canvas = new Canvas(shell, SWT.BORDER);
	canvas.setBounds(40, 40, 100, 40);
	final Shell popup1 = new Shell(shell, SWT.NO_TRIM);
	canvas.addMouseTrackListener(new MouseTrackAdapter() {
		public void mouseEnter(MouseEvent event) {
			try {
			//Thread.currentThread().sleep(500); //Use on windows 
to slow down the UI and make the OS queue multiple MouseMoves
			} catch (Exception e){
				e.printStackTrace();
			}
			showPopup(canvas, popup1);
		}
		public void mouseExit(MouseEvent event) {
			//disposePopup(popup);
		}
	});

	final Canvas canvas2 = new Canvas(shell, SWT.BORDER);
	canvas2.setBounds(40,100,100,40);
	final Shell popup2 = new Shell(shell, SWT.NO_TRIM);
	canvas2.addMouseTrackListener(new MouseTrackAdapter() {
		public void mouseEnter(MouseEvent event) {
			showPopup(canvas2, popup2);
		}
		public void mouseExit(MouseEvent event) {
			//disposePopup(popup);
		}
	});

	
	shell.open();
	Display display = Display.getDefault();
	while (!shell.isDisposed())
		if (!display.readAndDispatch())
			display.sleep();
	shell.dispose();
}

public void disposePopup(Shell popup) {
	if (!popup.isVisible())
		return;
	popup.setCapture(false);
	captureCount --;
	System.out.println("Capture at " + captureCount);
	popup.setVisible(false);
}

public void showPopup(Canvas canvas, final Shell popup) {
	Point p = new Point(0,0);
	p = canvas.toDisplay(p);
	Point d = canvas.getSize();
	Rectangle rect = new Rectangle(p.x, p.y, d.x+20, d.y);
	popup.setBounds(rect);
	popup.setBackground(new Color(null, 255,0,0));
	popup.addMouseTrackListener(new MouseTrackAdapter() {
		public void mouseExit(MouseEvent event) {
			disposePopup(popup);
		}
	});	
	popup.addMouseMoveListener(new MouseMoveListener() {
		public void mouseMove(MouseEvent event) {
			Point p = popup.toDisplay(new Point(event.x, event.y));
			if (!popup.getBounds().contains(p))
				disposePopup(popup);
		}
	});
	popup.setVisible(true);
	popup.setCapture(true);
	captureCount++;
	System.out.println("Capture at " + captureCount);

}

public class PopupHelper {
	protected Canvas canvas;
	public PopupHelper(Canvas canvas) {
		this.canvas = canvas;
	}
}

}
Comment 1 Randy Hudson CLA 2002-06-05 17:25:10 EDT
Assigned to wrong component.  Please change "Assigned To:" for me, since I 
don't have authority.
Comment 2 Randy Hudson CLA 2002-06-13 20:55:13 EDT
Any updates on this?
Comment 3 Grant Gayed CLA 2002-06-14 08:36:39 EDT
Will be addressed post-2.0
Comment 4 Randy Hudson CLA 2002-06-14 09:12:23 EDT
I'm wasn't 100% sure that I was correct.  So you are confirming that by calling 
setCapture(true) on a control, other controls should not receive mouse events 
until either setCapture(false) is called, or the mouse is clicked (which 
automatically releases capture)?
Comment 5 Grant Gayed CLA 2002-06-14 11:38:19 EDT
I just spoke with SN.  Events that have been queued for other widgets can be 
delivered to them after a setCapture(true) has been invoked on a Control.  One 
event that's guaranteed to be delivered is mouseExit to the control that loses 
focus when setCapture(true) is invoked.

Steve's suggestion is to run through the event loop when invoking setCapture() 
and remove these undesirable events.
Comment 6 Randy Hudson CLA 2002-06-14 12:19:20 EDT
Running through the pending events is not possible.  First, I don't think it is 
a good idea to run the event loop anywhere but in the main application loop.  
But, running these additional events will not solve my problem.  If I were to 
run the loop locally, the second canvas would then get its queued mouse enter, 
it would popup *its* shell, and setCapture to that.  Then, once there are no 
more pending events, the first canvas would popup its shell, and setCapture to 
it, leaving me with the original problem of having 2 shells popped up.  There 
is no way of knowing what these pending mouse events caused.

I understand that the original Canvas that received the mouseEnter, will 
therefore get a *single* mouseMove, and then the Exit when the popup Shell sets 
capture.  This makes sense.
Comment 7 Randy Hudson CLA 2002-06-19 14:41:25 EDT
Will this be fixed for 2.0 or post 2.0 (or never)?  I think it is pretty 
depressing to think that something like Canvas, which has no native function, 
will not be behave consistently across platforms.  See also Bug 20569 for 
keyboard irregularities.

I need to decide if I should disable GEF's rollover behavior in the next 24 
hours.
Comment 8 Grant Gayed CLA 2002-06-19 15:16:23 EDT
This won't be addressed for 2.0.
Comment 9 Randy Hudson CLA 2002-06-19 15:19:18 EDT
>This won't be addressed for 2.0
Actually, that wasn't one of the choices.  Please choose from:
a) 2.0
b) Post 2.0
c) Never.
Comment 10 Grant Gayed CLA 2002-06-19 15:46:39 EDT
Based on discussions with SN and SQ I'd say never.
Comment 11 Mike Wilson CLA 2002-06-19 16:36:53 EDT
Listen.

There have been several discussions about how this could be managed at the 
application end. Personally, I don't see why we can't implement exactly the 
same code (basically, a state machine for handling who has the capture in the 
event loop). Obviously, doing this would be extremely scary and we can't do 
something like that _right_now_. However, I want us to investigate that as 
soon as R2.0 ships, with an eye towards getting such a fix, or something 
equivalent, in for R2.1.

In the mean time, I asked SN to send Randy an example of how to workaround the 
problem, and by golly I expect him to do that. Tomorrow.

This PR is LATER, not WONTFIX!
Comment 12 Steve Northover CLA 2002-06-20 13:30:24 EDT
This is the first time I have actually seen this PR although I have talked to 
GG about it and have discussed "capture" issues with RH as far back as the last 
OTI TC.
Comment 13 Veronika Irvine CLA 2002-06-21 11:28:53 EDT
Proposed solution to rollover affect.  Issues with setCapture will be 
reinvestigated after R2.0.

Tested on Windows 2000, Linux Motif and Linux GTK.
Note setCapture is not required.

import org.eclipse.swt.*;
import org.eclipse.swt.custom.*;
import org.eclipse.swt.events.*;
import org.eclipse.swt.graphics.*;
import org.eclipse.swt.layout.*;
import org.eclipse.swt.widgets.*;

public class HoverOnTop {
	
	public static void main(String[] args) {
		final Display display = new Display();
		Shell shell = new Shell(display);
		shell.setLayout(new FillLayout());
		// Create tooltip
		final Shell tooltip = new Shell(shell, SWT.ON_TOP);
		tooltip.setLayout(new FillLayout());
		final Label l = new Label(tooltip, SWT.CENTER);
		Color tipForeground = display.getSystemColor
(SWT.COLOR_INFO_FOREGROUND);
		Color tipBackground = display.getSystemColor
(SWT.COLOR_INFO_BACKGROUND);
		l.setForeground(tipForeground);
		l.setBackground(tipBackground);
		final Runnable timer = new Runnable() {
			public void run() {
				if (tooltip.isDisposed()) return;	
				Control c = display.getCursorControl();
				if (c != l && c != tooltip) {
					tooltip.setVisible(false);
					return;
				}
				display.timerExec(400, this);
			}
		};

		// create controls where tooltip will appear
		ScrolledComposite sc = new ScrolledComposite(shell, 
SWT.H_SCROLL | SWT.V_SCROLL);
		Composite comp = new Composite(sc, SWT.NONE);
		comp.addDisposeListener(new DisposeListener() {
			public void widgetDisposed(DisposeEvent e) {
				if (!tooltip.isDisposed()) tooltip.dispose();
			}
		});
		sc.setContent(comp);
		comp.setLayout(new RowLayout(SWT.VERTICAL));
		for (int i = 0; i < 50; i++) {
			final Button b = new Button(comp, SWT.PUSH);
			b.setLayoutData(new RowData(200, SWT.DEFAULT));
			b.setText("Button "+i);
			b.addMouseTrackListener(new MouseTrackAdapter() {
			public void mouseEnter(MouseEvent e) {
				l.setText(b.getText());
				Rectangle bounds = b.getBounds();
				Point location = new Point(bounds.x, bounds.y);
				location = b.getParent().toDisplay(location);
				tooltip.setBounds(location.x, location.y, 
bounds.width, bounds.height);
				tooltip.setVisible(true);
				tooltip.layout(); // required due to bug on 
motif see 20792
				display.timerExec(400, timer);
			}
		});
		}
		Point preferredSize = comp.computeSize(SWT.DEFAULT, 
SWT.DEFAULT);
		comp.setSize(preferredSize);
		shell.setSize(200, 200);
			
		shell.open();
		while (!shell.isDisposed()) {
			if (!display.readAndDispatch())
				display.sleep();
		}
		display.dispose();
	}
}
Comment 14 Veronika Irvine CLA 2002-06-21 11:30:40 EDT
*** Bug 17509 has been marked as a duplicate of this bug. ***
Comment 15 Randy Hudson CLA 2002-06-21 12:02:55 EDT
>Control c = display.getCursorControl();
Wow, Display has so many cool utilities on it.  Nice workaround.

I'll have to change my code to have a singleton popup helper, which it probably 
should have had anyway.  Otherwise, I'll get "popup trails".
-------------Comments on post V2.0 fix--------------
SWT folks said:
One event that's guaranteed to be delivered is mouseExit to the control that 
loses focus [you mean "mouse focus"?] when setCapture(true) is invoked.

This would solve my problem *if* it were true.  Part of the problem is that 
capture was requested by a second popup when the first popup thought that it 
had capture.  Based on your statement, the first popup should get a MouseExit, 
but doesn't.  Well, this would solve my problem but I don't think it would be 
enough to make the platforms consistent.  Thanks again for the workaround.
Comment 16 Steve Northover CLA 2002-06-21 12:38:01 EDT
Not sure who said what about setCapture() and mouse exit but we need to be 
suspicious of sending free events, that is doing something that the OS does 
not.  I believe the "correct" fix will involve emulating the X behavior on 
Windows but this is premature.
Comment 17 Randy Hudson CLA 2002-08-16 09:40:00 EDT
This is causing me problems once again.

Create a shell, add a mouse listener to it.  Give the Shell a popup menu.  On 
mouse down, setCapture to the Shell.

On windows, this works fine, on Motif, the setCapture() seems to take effect 
asynchronously.  Despite the mousedown coming first, the capture is set after 
the popup menu is shown.  The popup menu is not functional as a result.
Comment 18 Steve Northover CLA 2002-08-22 15:07:20 EDT
Randy, this is a different problem (you get an X error).  Please enter a 
different PR for it.  Thanks.
Comment 19 Randy Hudson CLA 2002-08-22 15:20:40 EDT
http://bugs.eclipse.org/bugs/show_bug.cgi?id=22737

This seems similar to me.  Basically, setCapture() does not happen soon enough, 
or it hasn't happened when setCapture(true) returns.  If I modify my test case 
to setCapture on MouseEnter instead of mousedown, the context menu works fine, 
because setCapture has more time to take effect.
Comment 20 Steve Northover CLA 2002-08-22 17:01:57 EDT
The problem in 22737 was that the menu attempted to get a grab and failed but 
still popped up, causing bizzare behavoir.  This PR is something different.
Comment 21 Mike Wilson CLA 2008-04-13 09:38:01 EDT
Closing for lack of activity. Please re-open if there is any intent to fix this at some point.