/* ******************************************************************************
* Copyright (c) 2006-2010 XMind Ltd. and others.
*
* This file is a part of XMind 3. XMind releases 3 and
* above are dual-licensed under the Eclipse Public License (EPL),
* which is available at http://www.eclipse.org/legal/epl-v10.html
* and the GNU Lesser General Public License (LGPL),
* which is available at http://www.gnu.org/licenses/lgpl.html
* See http://www.xmind.net/license.html for details.
*
* Contributors:
* XMind Ltd. - initial API and implementation
*******************************************************************************/
package org.xmind.ui.texteditor;
import java.util.ArrayList;
import org.eclipse.core.runtime.Assert;
import org.eclipse.core.runtime.ListenerList;
import org.eclipse.jface.bindings.keys.KeyStroke;
import org.eclipse.jface.dialogs.PopupDialog;
import org.eclipse.jface.fieldassist.IContentProposal;
import org.eclipse.jface.fieldassist.IContentProposalListener;
import org.eclipse.jface.fieldassist.IContentProposalListener2;
import org.eclipse.jface.fieldassist.IContentProposalProvider;
import org.eclipse.jface.fieldassist.IControlContentAdapter;
import org.eclipse.jface.viewers.ILabelProvider;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.StyledText;
import org.eclipse.swt.events.DisposeEvent;
import org.eclipse.swt.events.DisposeListener;
import org.eclipse.swt.events.FocusAdapter;
import org.eclipse.swt.events.FocusEvent;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.events.SelectionListener;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.widgets.Composite;
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.swt.widgets.TableItem;
import org.eclipse.swt.widgets.Text;
/**
* ContentProposalAdapter can be used to attach content proposal behavior to a
* control. This behavior includes obtaining proposals, opening a popup dialog,
* managing the content of the control relative to the selections in the popup,
* and optionally opening up a secondary popup to further describe proposals.
* <p>
* A number of configurable options are provided to determine how the control
* content is altered when a proposal is chosen, how the content proposal popup
* is activated, and whether any filtering should be done on the proposals as
* the user types characters.
* <p>
* This class is not intended to be subclassed.
* <p>
* <b>ATTN</b>: Frank Shaka has modified part of this class to adapt it for
* XMIND.
*
* @since 3.2
*/
public class ContentProposalAdapter {
/**
* Tests if the popup has focus.
*
* @return whether the popup has focus.
*/
// ATTN: Frank added this method.
public boolean isPopupHasFocus() {
if (!isValid() || popup == null)
return false;
if (scrollbarClicked || popup.hasFocus()
|| (popup.infoPopup != null && popup.infoPopup.hasFocus()))
return true;
Shell activeShell = Display.getCurrent().getActiveShell();
if (activeShell == popup.getShell()
|| (popup.infoPopup != null && popup.infoPopup.getShell() == activeShell)) {
return true;
}
return false;
}
// ATTN: Frank moved this field to here from within
// ContentProposalPopup.PopupCloserListener.
private boolean scrollbarClicked = false;
/*
* The lightweight popup used to show content proposals for a text field. If
* additional information exists for a proposal, then selecting that
* proposal will result in the information being displayed in a secondary
* popup.
*/
class ContentProposalPopup extends PopupDialog {
/*
* The listener we install on the popup and related controls to
* determine when to close the popup. Some events (move, resize, close,
* deactivate) trigger closure as soon as they are received, simply
* because one of the registered listeners received them. Other events
* depend on additional circumstances.
*/
private final class PopupCloserListener implements Listener {
public void handleEvent(final Event e) {
// If focus is leaving an important widget or the field's
// shell is deactivating
if (e.type == SWT.FocusOut) {
scrollbarClicked = false;
/*
* Ignore this event if it's only happening because focus is
* moving between the popup shells, their controls, or a
* scrollbar. Do this in an async since the focus is not
* actually switched when this event is received.
*/
e.display.asyncExec(new Runnable() {
public void run() {
if (isValid()) {
if (scrollbarClicked
|| hasFocus()
|| (infoPopup != null && infoPopup
.hasFocus())) {
return;
}
// Workaround a problem on X and Mac, whereby at
// this point, the focus control is not known.
// This can happen, for example, when resizing
// the popup shell on the Mac.
// Check the active shell.
Shell activeShell = e.display.getActiveShell();
if (activeShell == getShell()
|| (infoPopup != null && infoPopup
.getShell() == activeShell)) {
return;
}
/*
* System.out.println(e);
* System.out.println(e.display
* .getFocusControl());
* System.out.println(e.display
* .getActiveShell());
*/
close();
}
}
});
return;
}
// Scroll bar has been clicked. Remember this for focus event
// processing.
if (e.type == SWT.Selection) {
scrollbarClicked = true;
return;
}
// For all other events, merely getting them dictates closure.
close();
}
// Install the listeners for events that need to be monitored for
// popup closure.
void installListeners() {
// Listeners on this popup's table and scroll bar
proposalTable.addListener(SWT.FocusOut, this);
ScrollBar scrollbar = proposalTable.getVerticalBar();
if (scrollbar != null) {
scrollbar.addListener(SWT.Selection, this);
}
// Listeners on this popup's shell
getShell().addListener(SWT.Dea