Skip to main content
aboutsummaryrefslogtreecommitdiffstats
blob: 0167a56f916b7888c9fa036bcccf30e1684d20cc (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
/*******************************************************************************
 * Copyright (c) 2000, 2018 IBM Corporation and others.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     IBM Corporation - initial API and implementation
 *******************************************************************************/
package org.eclipse.jface.text.contentassist;

import org.eclipse.swt.SWT;
import org.eclipse.swt.events.FocusEvent;
import org.eclipse.swt.events.FocusListener;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.events.SelectionListener;
import org.eclipse.swt.events.ShellAdapter;
import org.eclipse.swt.events.ShellEvent;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.Listener;
import org.eclipse.swt.widgets.ScrollBar;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.Table;

import org.eclipse.jface.internal.text.DelayedInputChangeListener;
import org.eclipse.jface.internal.text.InformationControlReplacer;

import org.eclipse.jface.text.IDelayedInputChangeProvider;
import org.eclipse.jface.text.IInformationControl;
import org.eclipse.jface.text.IInformationControlExtension5;
import org.eclipse.jface.text.IInputChangedListener;


/**
 * A generic closer class used to monitor various
 * interface events in order to determine whether
 * a content assistant should be terminated and all
 * associated windows be closed.
 */
class PopupCloser extends ShellAdapter implements FocusListener, SelectionListener, Listener {

	/** The content assistant to be monitored. */
	private ContentAssistant fContentAssistant;
	/** The table of a selector popup opened by the content assistant. */
	private Table fTable;
	/** The scroll bar of the table for the selector popup. */
	private ScrollBar fScrollbar;
	/** Indicates whether the scroll bar thumb has been grabbed. */
	private boolean fScrollbarClicked= false;
	/**
	 * The shell on which some listeners are registered.
	 * @since 3.1
	 */
	private Shell fShell;
	/**
	 * The display on which some filters are registered.
	 * @since 3.4
	 */
	private Display fDisplay;
	/**
	 * The additional info controller, or <code>null</code>.
	 * @since 3.4
	 */
	private AdditionalInfoController fAdditionalInfoController;

	/**
	 * Installs this closer on the given table opened by the given content assistant.
	 *
	 * @param contentAssistant the content assistant
	 * @param table the table to be tracked
	 */
	public void install(ContentAssistant contentAssistant, Table table) {
		install(contentAssistant, table, null);
	}

	/**
	 * Installs this closer on the given table opened by the given content assistant.
	 *
	 * @param contentAssistant the content assistant
	 * @param table the table to be tracked
	 * @param additionalInfoController the additional info controller, or <code>null</code>
	 * @since 3.4
	 */
	public void install(ContentAssistant contentAssistant, Table table, AdditionalInfoController additionalInfoController) {
		fContentAssistant= contentAssistant;
		fTable= table;
		fAdditionalInfoController= additionalInfoController;

		if (Helper.okToUse(fTable)) {
			fShell= fTable.getShell();
			fDisplay= fShell.getDisplay();

			fShell.addShellListener(this);
			fTable.addFocusListener(this);
			fScrollbar= fTable.getVerticalBar();
			if (fScrollbar != null)
				fScrollbar.addSelectionListener(this);

			fDisplay.addFilter(SWT.Activate, this);
			fDisplay.addFilter(SWT.MouseVerticalWheel, this);

			fDisplay.addFilter(SWT.Deactivate, this);
			fDisplay.addFilter(SWT.MouseUp, this);
		}
	}

	/**
	 * Uninstalls this closer if previously installed.
	 */
	public void uninstall() {
		fContentAssistant= null;
		if (Helper.okToUse(fShell))
			fShell.removeShellListener(this);
		fShell= null;
		if (Helper.okToUse(fScrollbar))
			fScrollbar.removeSelectionListener(this);
		if (Helper.okToUse(fTable))
			fTable.removeFocusListener(this);
		if (fDisplay != null && ! fDisplay.isDisposed()) {
			fDisplay.removeFilter(SWT.Activate, this);
			fDisplay.removeFilter(SWT.MouseVerticalWheel, this);

			fDisplay.removeFilter(SWT.Deactivate, this);
			fDisplay.removeFilter(SWT.MouseUp, this);
		}
	}

	@Override
	public void widgetSelected(SelectionEvent e) {
		fScrollbarClicked= true;
	}

	@Override
	public void widgetDefaultSelected(SelectionEvent e) {
		fScrollbarClicked= true;
	}

	@Override
	public void focusGained(FocusEvent e) {
	}

	@Override
	public void focusLost(final FocusEvent e) {
		fScrollbarClicked= false;
		Display d= fTable.getDisplay();
		d.asyncExec(() -> {
			if (Helper.okToUse(fTable) && !fTable.isFocusControl() && !fScrollbarClicked && fContentAssistant != null)
				fContentAssistant.popupFocusLost(e);
		});
	}

	@Override
	public void shellDeactivated(ShellEvent e) {
		if (fContentAssistant != null && fDisplay != null) {
			fDisplay.asyncExec(() -> {
				/*
				 * The asyncExec is a workaround for https://bugs.eclipse.org/bugs/show_bug.cgi?id=235556 :
				 * fContentAssistant.hasProposalPopupFocus() is still true during the shellDeactivated(..) event.
				 */
				if (fContentAssistant != null && !fContentAssistant.hasProposalPopupFocus())
					fContentAssistant.hide();
			});
		}
	}


	@Override
	public void shellClosed(ShellEvent e) {
		if (fContentAssistant != null)
			fContentAssistant.hide();
	}

	@Override
	public void handleEvent(Event event) {
		switch (event.type) {
			case SWT.Activate:
			case SWT.MouseVerticalWheel:
				if (fAdditionalInfoController == null)
					return;
				if (event.widget == fShell || event.widget == fTable || event.widget == fScrollbar)
					return;

				if (fAdditionalInfoController.getInternalAccessor().getInformationControlReplacer() == null)
					fAdditionalInfoController.hideInformationControl();
				else if (!fAdditionalInfoController.getInternalAccessor().isReplaceInProgress()) {
					IInformationControl infoControl= fAdditionalInfoController.getCurrentInformationControl2();
					// During isReplaceInProgress(), events can come from the replacing information control
					if (event.widget instanceof Control && infoControl instanceof IInformationControlExtension5) {
						Control control= (Control) event.widget;
						IInformationControlExtension5 iControl5= (IInformationControlExtension5) infoControl;
						if (!(iControl5.containsControl(control)))
							fAdditionalInfoController.hideInformationControl();
						else if (event.type == SWT.MouseVerticalWheel)
							fAdditionalInfoController.getInternalAccessor().replaceInformationControl(false);
					} else if (infoControl != null && infoControl.isFocusControl()) {
						fAdditionalInfoController.getInternalAccessor().replaceInformationControl(true);
					}
				}
				break;

			case SWT.MouseUp:
				if (fAdditionalInfoController == null || fAdditionalInfoController.getInternalAccessor().isReplaceInProgress())
					break;
				if (event.widget instanceof Control) {
					Control control= (Control) event.widget;
					IInformationControl infoControl= fAdditionalInfoController.getCurrentInformationControl2();
					if (infoControl instanceof IInformationControlExtension5) {
						final IInformationControlExtension5 iControl5= (IInformationControlExtension5) infoControl;
						if (iControl5.containsControl(control)) {
							if (infoControl instanceof IDelayedInputChangeProvider) {
								final IDelayedInputChangeProvider delayedICP= (IDelayedInputChangeProvider) infoControl;
								final IInputChangedListener inputChangeListener= new DelayedInputChangeListener(delayedICP, fAdditionalInfoController.getInternalAccessor().getInformationControlReplacer());
								delayedICP.setDelayedInputChangeListener(inputChangeListener);
								// cancel automatic input updating after a small timeout:
								control.getShell().getDisplay().timerExec(1000, () -> delayedICP.setDelayedInputChangeListener(null));
							}

							// XXX: workaround for https://bugs.eclipse.org/bugs/show_bug.cgi?id=212392 :
							control.getShell().getDisplay().asyncExec(() -> fAdditionalInfoController.getInternalAccessor().replaceInformationControl(true));
						}
					}
				}
				break;

			case SWT.Deactivate:
				if (fAdditionalInfoController == null)
					break;
				InformationControlReplacer replacer= fAdditionalInfoController.getInternalAccessor().getInformationControlReplacer();
				if (replacer != null && fContentAssistant != null) {
					IInformationControl iControl= replacer.getCurrentInformationControl2();
					if (event.widget instanceof Control && iControl instanceof IInformationControlExtension5) {
						Control control= (Control) event.widget;
						IInformationControlExtension5 iControl5= (IInformationControlExtension5) iControl;
						if (iControl5.containsControl(control)) {
							control.getDisplay().asyncExec(() -> {
								if (fContentAssistant != null && !fContentAssistant.hasProposalPopupFocus())
									fContentAssistant.hide();
							});
						}
					}
				}
				break;
		}
	}
}

Back to the top