Bug 94106: Fix hide/cleanup of content information popup stack

Change-Id: Ia35c3c860918d2713e29b11b409d7cacfe726fe7
Signed-off-by: Stephan Wahlbrink <sw@wahlbrink.eu>
diff --git a/org.eclipse.jface.text.tests/src/org/eclipse/jface/text/tests/contentassist/AbstractContentAssistTest.java b/org.eclipse.jface.text.tests/src/org/eclipse/jface/text/tests/contentassist/AbstractContentAssistTest.java
index c669281..dbdc270 100644
--- a/org.eclipse.jface.text.tests/src/org/eclipse/jface/text/tests/contentassist/AbstractContentAssistTest.java
+++ b/org.eclipse.jface.text.tests/src/org/eclipse/jface/text/tests/contentassist/AbstractContentAssistTest.java
@@ -26,8 +26,11 @@
 import org.junit.Assert;
 
 import org.eclipse.swt.SWT;
+import org.eclipse.swt.custom.ST;
 import org.eclipse.swt.custom.StyledText;
-import org.eclipse.swt.layout.FillLayout;
+import org.eclipse.swt.layout.GridData;
+import org.eclipse.swt.layout.GridLayout;
+import org.eclipse.swt.widgets.Button;
 import org.eclipse.swt.widgets.Event;
 import org.eclipse.swt.widgets.Shell;
 
@@ -48,6 +51,8 @@
 	private SourceViewer viewer;
 	private ContentAssistant assistant;
 	private Document document;
+	
+	private Button button;
 
 
 	public AbstractContentAssistTest() {
@@ -64,10 +69,11 @@
 
 	protected void setupSourceViewer(ContentAssistant contentAssistant, String initialText) {
 		shell= new Shell();
-		shell.setSize(500, 240);
-		shell.setLayout(new FillLayout());
+		shell.setSize(500, 280);
+		shell.setLayout(new GridLayout());
 		
 		viewer= new SourceViewer(shell, null, SWT.NONE);
+		viewer.getControl().setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
 		assistant= contentAssistant;
 		viewer.configure(createSourceViewerConfiguration());
 		
@@ -77,6 +83,9 @@
 		}
 		viewer.setDocument(document);
 		
+		button= new Button(shell, SWT.PUSH);
+		button.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false));
+		
 		shell.open();
 		Assert.assertTrue(new DisplayHelper() {
 			@Override
@@ -130,6 +139,41 @@
 		processEvents();
 	}
 
+	protected void postSourceViewerKeyEvent(char character, int stateMask, int type) {
+		processEvents();
+		Event event= new Event();
+		event.type= type;
+		event.widget= viewer.getTextWidget();
+		event.display= viewer.getTextWidget().getDisplay();
+		event.character= character;
+		event.stateMask= stateMask;
+		event.doit= true;
+		viewer.getTextWidget().notifyListeners(type, event);
+		processEvents();
+	}
+
+	protected void emulatePressArrowKey(int keyCode) {
+		switch (keyCode) {
+		case SWT.ARROW_LEFT:
+			viewer.getTextWidget().invokeAction(ST.COLUMN_PREVIOUS);
+			break;
+		case SWT.ARROW_RIGHT:
+			viewer.getTextWidget().invokeAction(ST.COLUMN_NEXT);
+			break;
+		case SWT.ARROW_UP:
+			viewer.getTextWidget().invokeAction(ST.LINE_UP);
+			break;
+		case SWT.ARROW_DOWN:
+			viewer.getTextWidget().invokeAction(ST.LINE_DOWN);
+			break;
+		}
+		postSourceViewerKeyEvent(keyCode, 0, ST.VerifyKey);
+	}
+
+	protected void emulatePressEscKey() {
+		postSourceViewerKeyEvent(SWT.ESC, 0, ST.VerifyKey);
+	}
+
 	protected void runTextOperation(int operation) {
 		ITextOperationTarget textOperationTarget= viewer.getTextOperationTarget();
 
@@ -142,6 +186,11 @@
 	}
 
 
+	public Button getButton() {
+		return button;
+	}
+
+
 	protected void processEvents() {
 		DisplayHelper.driveEventQueue(shell.getDisplay());
 	}
diff --git a/org.eclipse.jface.text.tests/src/org/eclipse/jface/text/tests/contentassist/ContextInformationPresenterTest.java b/org.eclipse.jface.text.tests/src/org/eclipse/jface/text/tests/contentassist/ContextInformationPresenterTest.java
index 80b8cef..31fa470 100644
--- a/org.eclipse.jface.text.tests/src/org/eclipse/jface/text/tests/contentassist/ContextInformationPresenterTest.java
+++ b/org.eclipse.jface.text.tests/src/org/eclipse/jface/text/tests/contentassist/ContextInformationPresenterTest.java
@@ -102,7 +102,7 @@
 				new StyleRange(0, 3, null, null, SWT.BOLD)
 		}, getInfoStyleRanges(this.infoShell));
 		
-		postSourceViewerKeyEvent(SWT.ARROW_RIGHT, 0, SWT.KeyDown);
+		emulatePressArrowKey(SWT.ARROW_RIGHT);
 		
 		assertEquals("idx= 0", getInfoText(this.infoShell));
 		assertArrayEquals(new StyleRange[] {
diff --git a/org.eclipse.jface.text.tests/src/org/eclipse/jface/text/tests/contentassist/ContextInformationTest.java b/org.eclipse.jface.text.tests/src/org/eclipse/jface/text/tests/contentassist/ContextInformationTest.java
index 1d5d6af..27a1f96 100644
--- a/org.eclipse.jface.text.tests/src/org/eclipse/jface/text/tests/contentassist/ContextInformationTest.java
+++ b/org.eclipse.jface.text.tests/src/org/eclipse/jface/text/tests/contentassist/ContextInformationTest.java
@@ -20,6 +20,7 @@
 
 import org.junit.Test;
 
+import org.eclipse.swt.SWT;
 import org.eclipse.swt.custom.StyledText;
 import org.eclipse.swt.widgets.Control;
 import org.eclipse.swt.widgets.Shell;
@@ -88,6 +89,92 @@
 		new Accessor(getContentAssistant(), ContentAssistant.class).invoke("hide", new Object[0]);
 	}
 
+	@Test
+	public void testContextInfo_hide_focusOut() throws Exception {
+		setupSourceViewer(createBarContentAssist(), BarContentAssistProcessor.PROPOSAL);
+		
+		final List<Shell> beforeShells = getCurrentShells();
+		
+		selectAndReveal(4, 0);
+		processEvents();
+		
+		triggerContextInformation();
+		this.infoShell= findNewShell(beforeShells);
+		assertEquals("idx= 0", getInfoText(this.infoShell));
+		
+		selectAndReveal(8, 0);
+		processEvents();
+		
+		triggerContextInformation();
+		this.infoShell= findNewShell(beforeShells);
+		assertEquals("idx= 1", getInfoText(this.infoShell));
+		
+		// Hide all
+		getButton().setFocus();
+		processEvents();
+		assertTrue(this.infoShell.isDisposed() || !this.infoShell.isVisible());
+	}
+
+	@Test
+	public void testContextInfo_hide_keyEsc() throws Exception {
+		setupSourceViewer(createBarContentAssist(), BarContentAssistProcessor.PROPOSAL);
+		
+		final List<Shell> beforeShells = getCurrentShells();
+		
+		selectAndReveal(4, 0);
+		processEvents();
+		
+		triggerContextInformation();
+		this.infoShell= findNewShell(beforeShells);
+		assertEquals("idx= 0", getInfoText(this.infoShell));
+		
+		selectAndReveal(8, 0);
+		processEvents();
+		
+		triggerContextInformation();
+		this.infoShell= findNewShell(beforeShells);
+		assertEquals("idx= 1", getInfoText(this.infoShell));
+		
+		emulatePressEscKey();
+		processEvents();
+		this.infoShell= findNewShell(beforeShells);
+		assertEquals("idx= 0", getInfoText(this.infoShell));
+		
+		emulatePressEscKey();
+		processEvents();
+		assertTrue(this.infoShell.isDisposed() || !this.infoShell.isVisible());
+	}
+
+	@Test
+	public void testContextInfo_hide_validRange() throws Exception {
+		setupSourceViewer(createBarContentAssist(), BarContentAssistProcessor.PROPOSAL + '\n');
+		
+		final List<Shell> beforeShells = getCurrentShells();
+		
+		selectAndReveal(4, 0);
+		processEvents();
+		
+		triggerContextInformation();
+		this.infoShell= findNewShell(beforeShells);
+		assertEquals("idx= 0", getInfoText(this.infoShell));
+		
+		selectAndReveal(8, 0);
+		processEvents();
+		
+		triggerContextInformation();
+		this.infoShell= findNewShell(beforeShells);
+		assertEquals("idx= 1", getInfoText(this.infoShell));
+		
+		emulatePressArrowKey(SWT.ARROW_LEFT);
+		processEvents();
+		this.infoShell= findNewShell(beforeShells);
+		assertEquals("idx= 0", getInfoText(this.infoShell));
+		
+		emulatePressArrowKey(SWT.ARROW_DOWN);
+		processEvents();
+		assertTrue(this.infoShell.isDisposed() || !this.infoShell.isVisible());
+	}
+
 
 	static String getInfoText(final Shell shell) {
 		assertTrue(shell.isVisible());
diff --git a/org.eclipse.jface.text/src/org/eclipse/jface/text/contentassist/ContextInformationPopup.java b/org.eclipse.jface.text/src/org/eclipse/jface/text/contentassist/ContextInformationPopup.java
index 742d869..7bf6d32 100644
--- a/org.eclipse.jface.text/src/org/eclipse/jface/text/contentassist/ContextInformationPopup.java
+++ b/org.eclipse.jface.text/src/org/eclipse/jface/text/contentassist/ContextInformationPopup.java
@@ -11,6 +11,7 @@
  * Contributors:
  *     IBM Corporation - initial API and implementation
  *     Stephan Wahlbrink <sw@wahlbrink.eu> - Bug 512251 - Fix IllegalArgumentException in ContextInformationPopup
+ *     Stephan Wahlbrink <sw@wahlbrink.eu> - Bug 94106 - Fix hide/cleanup of content information popup stack
  *******************************************************************************/
 package org.eclipse.jface.text.contentassist;
 
@@ -460,8 +461,11 @@
 
 	/**
 	 * Hides the context information popup.
+	 *
+	 * @param all <code>true</code> to hide popups at all,
+	 *     <code>false</code> to restore previous context frame if possible
 	 */
-	private void hideContextInfoPopup() {
+	private void hideContextInfoPopup(boolean all) {
 
 		if (Helper.okToUse(fContextInfoPopup)) {
 
@@ -470,7 +474,7 @@
 				fLastContext= fContextFrameStack.pop();
 				-- size;
 
-				if (size > 0) {
+				if (size > 0 && !all) {
 					ContextFrame current= fContextFrameStack.peek();
 					if (canShowFrame(current)) {
 						internalShowContextFrame(current, false);
@@ -478,9 +482,6 @@
 					}
 					// continue - try next
 				}
-				else {
-					break;
-				}
 			}
 			fContentAssistant.removeContentAssistListener(this, ContentAssistant.CONTEXT_INFO_POPUP);
 
@@ -702,7 +703,7 @@
 	 */
 	public void hide() {
 		hideContextSelector();
-		hideContextInfoPopup();
+		hideContextInfoPopup(true);
 	}
 
 	/**
@@ -815,13 +816,13 @@
 					break;
 				default:
 					if (e.keyCode != SWT.CAPS_LOCK && e.keyCode != SWT.MOD1 && e.keyCode != SWT.MOD2 && e.keyCode != SWT.MOD3 && e.keyCode != SWT.MOD4)
-						hideContextInfoPopup();
+						hideContextInfoPopup(true);
 					break;
 			}
 
 		} else if (key == SWT.ESC) {
 			e.doit= false;
-			hideContextInfoPopup();
+			hideContextInfoPopup(false);
 		} else {
 			validateContextInformation();
 		}
@@ -888,7 +889,7 @@
 					while (Helper.okToUse(fContextInfoPopup) && !fContextFrameStack.isEmpty()) {
 						ContextFrame top= fContextFrameStack.peek();
 						if (top.fValidator == null || !top.fValidator.isContextInformationValid(offset)) {
-							hideContextInfoPopup(); // loop variant: reduces the number of contexts on the stack
+							hideContextInfoPopup(false); // loop variant: reduces the number of contexts on the stack
 						} else if (top.fPresenter != null && top.fPresenter.updatePresentation(offset, fTextPresentation)) {
 							int widgetOffset= fContentAssistSubjectControlAdapter.getWidgetSelectionRange().x;
 							TextPresentation.applyTextPresentation(fTextPresentation, fContextInfoText);