# HG changeset patch
# User Frank Benoit
+ * 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.
+ *
+ * This class is not intended to be subclassed.
+ *
+ * @since 3.2
+ */
+public class ContentProposalAdapter {
+
+ /*
+ * 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 : 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 : Listener {
+ private bool scrollbarClicked = false;
+
+ public void handleEvent(Event e) {
+
+ // If focus is leaving an important widget or the field's
+ // shell is deactivating
+ if (e.type is DWT.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 class Runnable {
+ Event e_;
+ this(){ e_=e; }
+ public void run() {
+ if (isValid()) {
+ if (scrollbarClicked
+ || hasFocus()
+ || (infoPopup !is 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 is getShell()
+ || (infoPopup !is null && infoPopup
+ .getShell() is 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 is DWT.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(DWT.FocusOut, this);
+ ScrollBar scrollbar = proposalTable.getVerticalBar();
+ if (scrollbar !is null) {
+ scrollbar.addListener(DWT.Selection, this);
+ }
+
+ // Listeners on this popup's shell
+ getShell().addListener(DWT.Deactivate, this);
+ getShell().addListener(DWT.Close, this);
+
+ // Listeners on the target control
+ control.addListener(DWT.MouseDoubleClick, this);
+ control.addListener(DWT.MouseDown, this);
+ control.addListener(DWT.Dispose, this);
+ control.addListener(DWT.FocusOut, this);
+ // Listeners on the target control's shell
+ Shell controlShell = control.getShell();
+ controlShell.addListener(DWT.Move, this);
+ controlShell.addListener(DWT.Resize, this);
+
+ }
+
+ // Remove installed listeners
+ void removeListeners() {
+ if (isValid()) {
+ proposalTable.removeListener(DWT.FocusOut, this);
+ ScrollBar scrollbar = proposalTable.getVerticalBar();
+ if (scrollbar !is null) {
+ scrollbar.removeListener(DWT.Selection, this);
+ }
+
+ getShell().removeListener(DWT.Deactivate, this);
+ getShell().removeListener(DWT.Close, this);
+ }
+
+ if (control !is null && !control.isDisposed()) {
+
+ control.removeListener(DWT.MouseDoubleClick, this);
+ control.removeListener(DWT.MouseDown, this);
+ control.removeListener(DWT.Dispose, this);
+ control.removeListener(DWT.FocusOut, this);
+
+ Shell controlShell = control.getShell();
+ controlShell.removeListener(DWT.Move, this);
+ controlShell.removeListener(DWT.Resize, this);
+ }
+ }
+ }
+
+ /*
+ * The listener we will install on the target control.
+ */
+ private final class TargetControlListener : Listener {
+ // Key events from the control
+ public void handleEvent(Event e) {
+ if (!isValid()) {
+ return;
+ }
+
+ char key = e.character;
+
+ // Traverse events are handled depending on whether the
+ // event has a character.
+ if (e.type is DWT.Traverse) {
+ // If the traverse event contains a legitimate character,
+ // then we must set doit false so that the widget will
+ // receive the key event. We return immediately so that
+ // the character is handled only in the key event.
+ // See https://bugs.eclipse.org/bugs/show_bug.cgi?id=132101
+ if (key !is 0) {
+ e.doit = false;
+ return;
+ }
+ // Traversal does not contain a character. Set doit true
+ // to indicate TRAVERSE_NONE will occur and that no key
+ // event will be triggered. We will check for navigation
+ // keys below.
+ e.detail = DWT.TRAVERSE_NONE;
+ e.doit = true;
+ } else {
+ // Default is to only propagate when configured that way.
+ // Some keys will always set doit to false anyway.
+ e.doit = propagateKeys;
+ }
+
+ // No character. Check for navigation keys.
+
+ if (key is 0) {
+ int newSelection = proposalTable.getSelectionIndex();
+ int visibleRows = (proposalTable.getSize().y / proposalTable
+ .getItemHeight()) - 1;
+ switch (e.keyCode) {
+ case DWT.ARROW_UP:
+ newSelection -= 1;
+ if (newSelection < 0) {
+ newSelection = proposalTable.getItemCount() - 1;
+ }
+ // Not typical - usually we get this as a Traverse and
+ // therefore it never propagates. Added for consistency.
+ if (e.type is DWT.KeyDown) {
+ // don't propagate to control
+ e.doit = false;
+ }
+
+ break;
+
+ case DWT.ARROW_DOWN:
+ newSelection += 1;
+ if (newSelection > proposalTable.getItemCount() - 1) {
+ newSelection = 0;
+ }
+ // Not typical - usually we get this as a Traverse and
+ // therefore it never propagates. Added for consistency.
+ if (e.type is DWT.KeyDown) {
+ // don't propagate to control
+ e.doit = false;
+ }
+
+ break;
+
+ case DWT.PAGE_DOWN:
+ newSelection += visibleRows;
+ if (newSelection >= proposalTable.getItemCount()) {
+ newSelection = proposalTable.getItemCount() - 1;
+ }
+ if (e.type is DWT.KeyDown) {
+ // don't propagate to control
+ e.doit = false;
+ }
+ break;
+
+ case DWT.PAGE_UP:
+ newSelection -= visibleRows;
+ if (newSelection < 0) {
+ newSelection = 0;
+ }
+ if (e.type is DWT.KeyDown) {
+ // don't propagate to control
+ e.doit = false;
+ }
+ break;
+
+ case DWT.HOME:
+ newSelection = 0;
+ if (e.type is DWT.KeyDown) {
+ // don't propagate to control
+ e.doit = false;
+ }
+ break;
+
+ case DWT.END:
+ newSelection = proposalTable.getItemCount() - 1;
+ if (e.type is DWT.KeyDown) {
+ // don't propagate to control
+ e.doit = false;
+ }
+ break;
+
+ // If received as a Traverse, these should propagate
+ // to the control as keydown. If received as a keydown,
+ // proposals should be recomputed since the cursor
+ // position has changed.
+ case DWT.ARROW_LEFT:
+ case DWT.ARROW_RIGHT:
+ if (e.type is DWT.Traverse) {
+ e.doit = false;
+ } else {
+ e.doit = true;
+ String contents = getControlContentAdapter()
+ .getControlContents(getControl());
+ // If there are no contents, changes in cursor
+ // position
+ // have no effect. Note also that we do not affect
+ // the filter
+ // text on ARROW_LEFT as we would with BS.
+ if (contents.length > 0) {
+ asyncRecomputeProposals(filterText);
+ }
+ }
+ break;
+
+ // Any unknown keycodes will cause the popup to close.
+ // Modifier keys are explicitly checked and ignored because
+ // they are not complete yet (no character).
+ default:
+ if (e.keyCode !is DWT.CAPS_LOCK && e.keyCode !is DWT.MOD1
+ && e.keyCode !is DWT.MOD2
+ && e.keyCode !is DWT.MOD3
+ && e.keyCode !is DWT.MOD4) {
+ close();
+ }
+ return;
+ }
+
+ // If any of these navigation events caused a new selection,
+ // then handle that now and return.
+ if (newSelection >= 0) {
+ selectProposal(newSelection);
+ }
+ return;
+ }
+
+ // key !is 0
+ // Check for special keys involved in cancelling, accepting, or
+ // filtering the proposals.
+ switch (key) {
+ case DWT.ESC:
+ e.doit = false;
+ close();
+ break;
+
+ case DWT.LF:
+ case DWT.CR:
+ e.doit = false;
+ Object p = cast(Object)getSelectedProposal();
+ if (p !is null) {
+ acceptCurrentProposal();
+ } else {
+ close();
+ }
+ break;
+
+ case DWT.TAB:
+ e.doit = false;
+ getShell().setFocus();
+ return;
+
+ case DWT.BS:
+ // Backspace should back out of any stored filter text
+ if (filterStyle !is FILTER_NONE) {
+ // We have no filter to back out of, so do nothing
+ if (filterText.length is 0) {
+ return;
+ }
+ // There is filter to back out of
+ filterText = filterText.substring(0, filterText
+ .length - 1);
+ asyncRecomputeProposals(filterText);
+ return;
+ }
+ // There is no filtering provided by us, but some
+ // clients provide their own filtering based on content.
+ // Recompute the proposals if the cursor position
+ // will change (is not at 0).
+ int pos = getControlContentAdapter().getCursorPosition(
+ getControl());
+ // We rely on the fact that the contents and pos do not yet
+ // reflect the result of the BS. If the contents were
+ // already empty, then BS should not cause
+ // a recompute.
+ if (pos > 0) {
+ asyncRecomputeProposals(filterText);
+ }
+ break;
+
+ default:
+ // If the key is a defined unicode character, and not one of
+ // the special cases processed above, update the filter text
+ // and filter the proposals.
+ if (CharacterIsDefined(key)) {
+ if (filterStyle is FILTER_CUMULATIVE) {
+ filterText = filterText ~ dcharToString(key);
+ } else if (filterStyle is FILTER_CHARACTER) {
+ filterText = dcharToString(key);
+ }
+ // Recompute proposals after processing this event.
+ asyncRecomputeProposals(filterText);
+ }
+ break;
+ }
+ }
+ }
+
+ /*
+ * Internal class used to implement the secondary popup.
+ */
+ private class InfoPopupDialog : PopupDialog {
+
+ /*
+ * The text control that displays the text.
+ */
+ private Text text;
+
+ /*
+ * The String shown in the popup.
+ */
+ private String contents = EMPTY;
+
+ /*
+ * Construct an info-popup with the specified parent.
+ */
+ this(Shell parent) {
+ super(parent, PopupDialog.HOVER_SHELLSTYLE, false, false,
+ false, false, null, null);
+ }
+
+ /*
+ * Create a text control for showing the info about a proposal.
+ */
+ protected Control createDialogArea(Composite parent) {
+ text = new Text(parent, DWT.MULTI | DWT.READ_ONLY | DWT.WRAP
+ | DWT.NO_FOCUS);
+
+ // Use the compact margins employed by PopupDialog.
+ GridData gd = new GridData(GridData.BEGINNING
+ | GridData.FILL_BOTH);
+ gd.horizontalIndent = PopupDialog.POPUP_HORIZONTALSPACING;
+ gd.verticalIndent = PopupDialog.POPUP_VERTICALSPACING;
+ text.setLayoutData(gd);
+ text.setText(contents);
+
+ // since DWT.NO_FOCUS is only a hint...
+ text.addFocusListener(new class FocusAdapter {
+ public void focusGained(FocusEvent event) {
+ this.outer.close();
+ }
+ });
+ return text;
+ }
+
+ /*
+ * Adjust the bounds so that we appear adjacent to our parent shell
+ */
+ protected void adjustBounds() {
+ Rectangle parentBounds = getParentShell().getBounds();
+ Rectangle proposedBounds;
+ // Try placing the info popup to the right
+ Rectangle rightProposedBounds = new Rectangle(parentBounds.x
+ + parentBounds.width
+ + PopupDialog.POPUP_HORIZONTALSPACING, parentBounds.y
+ + PopupDialog.POPUP_VERTICALSPACING,
+ parentBounds.width, parentBounds.height);
+ rightProposedBounds = getConstrainedShellBounds(rightProposedBounds);
+ // If it won't fit on the right, try the left
+ if (rightProposedBounds.intersects(parentBounds)) {
+ Rectangle leftProposedBounds = new Rectangle(parentBounds.x
+ - parentBounds.width - POPUP_HORIZONTALSPACING - 1,
+ parentBounds.y, parentBounds.width,
+ parentBounds.height);
+ leftProposedBounds = getConstrainedShellBounds(leftProposedBounds);
+ // If it won't fit on the left, choose the proposed bounds
+ // that fits the best
+ if (leftProposedBounds.intersects(parentBounds)) {
+ if (rightProposedBounds.x - parentBounds.x >= parentBounds.x
+ - leftProposedBounds.x) {
+ rightProposedBounds.x = parentBounds.x
+ + parentBounds.width
+ + PopupDialog.POPUP_HORIZONTALSPACING;
+ proposedBounds = rightProposedBounds;
+ } else {
+ leftProposedBounds.width = parentBounds.x
+ - POPUP_HORIZONTALSPACING
+ - leftProposedBounds.x;
+ proposedBounds = leftProposedBounds;
+ }
+ } else {
+ // use the proposed bounds on the left
+ proposedBounds = leftProposedBounds;
+ }
+ } else {
+ // use the proposed bounds on the right
+ proposedBounds = rightProposedBounds;
+ }
+ getShell().setBounds(proposedBounds);
+ }
+
+ /*
+ * Set the text contents of the popup.
+ */
+ void setContents(String newContents) {
+ if (newContents is null) {
+ newContents = EMPTY;
+ }
+ this.contents = newContents;
+ if (text !is null && !text.isDisposed()) {
+ text.setText(contents);
+ }
+ }
+
+ /*
+ * Return whether the popup has focus.
+ */
+ bool hasFocus() {
+ if (text is null || text.isDisposed()) {
+ return false;
+ }
+ return text.getShell().isFocusControl()
+ || text.isFocusControl();
+ }
+ }
+
+ /*
+ * The listener installed on the target control.
+ */
+ private Listener targetControlListener;
+
+ /*
+ * The listener installed in order to close the popup.
+ */
+ private PopupCloserListener popupCloser;
+
+ /*
+ * The table used to show the list of proposals.
+ */
+ private Table proposalTable;
+
+ /*
+ * The proposals to be shown (cached to avoid repeated requests).
+ */
+ private IContentProposal[] proposals;
+
+ /*
+ * Secondary popup used to show detailed information about the selected
+ * proposal..
+ */
+ private InfoPopupDialog infoPopup;
+
+ /*
+ * Flag indicating whether there is a pending secondary popup update.
+ */
+ private bool pendingDescriptionUpdate = false;
+
+ /*
+ * Filter text - tracked while popup is open, only if we are told to
+ * filter
+ */
+ private String filterText = EMPTY;
+
+ /**
+ * Constructs a new instance of this popup, specifying the control for
+ * which this popup is showing content, and how the proposals should be
+ * obtained and displayed.
+ *
+ * @param infoText
+ * Text to be shown in a lower info area, or
+ * null
.
+ * @param controlContentAdapter
+ * the IControlContentAdapter
used to obtain and
+ * update the control's contents. May not be null
.
+ * @param proposals
+ * the array of Strings representing valid content proposals for
+ * the field.
+ */
+ public this(Control control,
+ IControlContentAdapter controlContentAdapter, String[] proposals) {
+ proposalProvider = new SimpleContentProposalProvider(proposals);
+ proposalProvider.setFiltering(true);
+ adapter = new ContentProposalAdapter(control, controlContentAdapter,
+ proposalProvider, null, null);
+ adapter.setPropagateKeys(true);
+ adapter
+ .setProposalAcceptanceStyle(ContentProposalAdapter.PROPOSAL_REPLACE);
+ }
+
+ /**
+ * Set the Strings to be used as content proposals.
+ *
+ * @param proposals
+ * the array of Strings to be used as proposals.
+ */
+ public void setProposals(String[] proposals) {
+ proposalProvider.setProposals(proposals);
+ }
+}
diff -r 50b0163e18f8 -r f12d40e7da8f dwtx/jface/fieldassist/ComboContentAdapter.d
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/dwtx/jface/fieldassist/ComboContentAdapter.d Thu Apr 03 18:56:20 2008 +0200
@@ -0,0 +1,117 @@
+/*******************************************************************************
+ * Copyright (c) 2005, 2006 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
+ * Port to the D programming language:
+ * Frank Benoit null
if there is no info area.
+ */
+ this(String infoText, IContentProposal[] proposals) {
+ // IMPORTANT: Use of DWT.ON_TOP is critical here for ensuring
+ // that the target control retains focus on Mac and Linux. Without
+ // it, the focus will disappear, keystrokes will not go to the
+ // popup, and the popup closer will wrongly close the popup.
+ // On platforms where DWT.ON_TOP overrides DWT.RESIZE, we will live
+ // with this.
+ // See https://bugs.eclipse.org/bugs/show_bug.cgi?id=126138
+ super(control.getShell(), DWT.RESIZE | DWT.ON_TOP, false, false,
+ false, false, null, infoText);
+ this.proposals = proposals;
+ }
+
+ /*
+ * Overridden to force change of colors. See
+ * https://bugs.eclipse.org/bugs/show_bug.cgi?id=136244 (non-Javadoc)
+ *
+ * @see dwtx.jface.dialogs.PopupDialog#createContents(dwt.widgets.Composite)
+ */
+ protected Control createContents(Composite parent) {
+ Control contents = super.createContents(parent);
+ changeDefaultColors(parent);
+ return contents;
+ }
+
+ /*
+ * Set the colors of the popup. The contents have already been created.
+ */
+ private void changeDefaultColors(Control control) {
+ applyForegroundColor(getShell().getDisplay().getSystemColor(
+ DWT.COLOR_LIST_FOREGROUND), control);
+ applyBackgroundColor(getShell().getDisplay().getSystemColor(
+ DWT.COLOR_LIST_BACKGROUND), control);
+ }
+
+ /*
+ * Creates the content area for the proposal popup. This creates a table
+ * and places it inside the composite. The table will contain a list of
+ * all the proposals.
+ *
+ * @param parent The parent composite to contain the dialog area; must
+ * not be null
.
+ */
+ protected final Control createDialogArea(Composite parent) {
+ // Use virtual where appropriate (see flag definition).
+ if (USE_VIRTUAL) {
+ proposalTable = new Table(parent, DWT.H_SCROLL | DWT.V_SCROLL
+ | DWT.VIRTUAL);
+
+ Listener listener = new class Listener {
+ public void handleEvent(Event event) {
+ handleSetData(event);
+ }
+ };
+ proposalTable.addListener(DWT.SetData, listener);
+ } else {
+ proposalTable = new Table(parent, DWT.H_SCROLL | DWT.V_SCROLL);
+ }
+
+ // set the proposals to force population of the table.
+ setProposals(filterProposals(proposals, filterText));
+
+ proposalTable.setHeaderVisible(false);
+ proposalTable.addSelectionListener(new class SelectionListener {
+
+ public void widgetSelected(SelectionEvent e) {
+ // If a proposal has been selected, show it in the secondary
+ // popup. Otherwise close the popup.
+ if (e.item is null) {
+ if (infoPopup !is null) {
+ infoPopup.close();
+ }
+ } else {
+ showProposalDescription();
+ }
+ }
+
+ // Default selection was made. Accept the current proposal.
+ public void widgetDefaultSelected(SelectionEvent e) {
+ acceptCurrentProposal();
+ }
+ });
+ return proposalTable;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see dwtx.jface.dialogs.PopupDialog.adjustBounds()
+ */
+ protected void adjustBounds() {
+ // Get our control's location in display coordinates.
+ Point location = control.getDisplay().map(control.getParent(),
+ null, control.getLocation());
+ int initialX = location.x + POPUP_OFFSET;
+ int initialY = location.y + control.getSize().y + POPUP_OFFSET;
+ // If we are inserting content, use the cursor position to
+ // position the control.
+ if (getProposalAcceptanceStyle() is PROPOSAL_INSERT) {
+ Rectangle insertionBounds = controlContentAdapter
+ .getInsertionBounds(control);
+ initialX = initialX + insertionBounds.x;
+ initialY = location.y + insertionBounds.y
+ + insertionBounds.height;
+ }
+
+ // If there is no specified size, force it by setting
+ // up a layout on the table.
+ if (popupSize is null) {
+ GridData data = new GridData(GridData.FILL_BOTH);
+ data.heightHint = proposalTable.getItemHeight()
+ * POPUP_CHAR_HEIGHT;
+ data.widthHint = Math.max(control.getSize().x,
+ POPUP_MINIMUM_WIDTH);
+ proposalTable.setLayoutData(data);
+ getShell().pack();
+ popupSize = getShell().getSize();
+ }
+ getShell().setBounds(initialX, initialY, popupSize.x, popupSize.y);
+
+ // Now set up a listener to monitor any changes in size.
+ getShell().addListener(DWT.Resize, new class Listener {
+ public void handleEvent(Event e) {
+ popupSize = getShell().getSize();
+ if (infoPopup !is null) {
+ infoPopup.adjustBounds();
+ }
+ }
+ });
+ }
+
+ /*
+ * Handle the set data event. Set the item data of the requested item to
+ * the corresponding proposal in the proposal cache.
+ */
+ private void handleSetData(Event event) {
+ TableItem item = cast(TableItem) event.item;
+ int index = proposalTable.indexOf(item);
+
+ if (0 <= index && index < proposals.length) {
+ IContentProposal current = proposals[index];
+ item.setText(getString(current));
+ item.setImage(getImage(current));
+ item.setData(cast(Object)current);
+ } else {
+ // this should not happen, but does on win32
+ }
+ }
+
+ /*
+ * Caches the specified proposals and repopulates the table if it has
+ * been created.
+ */
+ private void setProposals(IContentProposal[] newProposals) {
+ if (newProposals is null || newProposals.length is 0) {
+ newProposals = getEmptyProposalArray();
+ }
+ this.proposals = newProposals;
+
+ // If there is a table
+ if (isValid()) {
+ final int newSize = newProposals.length;
+ if (USE_VIRTUAL) {
+ // Set and clear the virtual table. Data will be
+ // provided in the DWT.SetData event handler.
+ proposalTable.setItemCount(newSize);
+ proposalTable.clearAll();
+ } else {
+ // Populate the table manually
+ proposalTable.setRedraw(false);
+ proposalTable.setItemCount(newSize);
+ TableItem[] items = proposalTable.getItems();
+ for (int i = 0; i < items.length; i++) {
+ TableItem item = items[i];
+ IContentProposal proposal = newProposals[i];
+ item.setText(getString(proposal));
+ item.setImage(getImage(proposal));
+ item.setData(cast(Object)proposal);
+ }
+ proposalTable.setRedraw(true);
+ }
+ // Default to the first selection if there is content.
+ if (newProposals.length > 0) {
+ selectProposal(0);
+ } else {
+ // No selection, close the secondary popup if it was open
+ if (infoPopup !is null) {
+ infoPopup.close();
+ }
+
+ }
+ }
+ }
+
+ /*
+ * Get the string for the specified proposal. Always return a String of
+ * some kind.
+ */
+ private String getString(IContentProposal proposal) {
+ if (proposal is null) {
+ return EMPTY;
+ }
+ if (labelProvider is null) {
+ return proposal.getLabel() is null ? proposal.getContent()
+ : proposal.getLabel();
+ }
+ return labelProvider.getText(cast(Object)proposal);
+ }
+
+ /*
+ * Get the image for the specified proposal. If there is no image
+ * available, return null.
+ */
+ private Image getImage(IContentProposal proposal) {
+ if (proposal is null || labelProvider is null) {
+ return null;
+ }
+ return labelProvider.getImage(cast(Object)proposal);
+ }
+
+ /*
+ * Return an empty array. Used so that something always shows in the
+ * proposal popup, even if no proposal provider was specified.
+ */
+ private IContentProposal[] getEmptyProposalArray() {
+ return new IContentProposal[0];
+ }
+
+ /*
+ * Answer true if the popup is valid, which means the table has been
+ * created and not disposed.
+ */
+ private bool isValid() {
+ return proposalTable !is null && !proposalTable.isDisposed();
+ }
+
+ /*
+ * Return whether the receiver has focus.
+ */
+ private bool hasFocus() {
+ if (!isValid()) {
+ return false;
+ }
+ return getShell().isFocusControl()
+ || proposalTable.isFocusControl();
+ }
+
+ /*
+ * Return the current selected proposal.
+ */
+ private IContentProposal getSelectedProposal() {
+ if (isValid()) {
+ int i = proposalTable.getSelectionIndex();
+ if (proposals is null || i < 0 || i >= proposals.length) {
+ return null;
+ }
+ return proposals[i];
+ }
+ return null;
+ }
+
+ /*
+ * Select the proposal at the given index.
+ */
+ private void selectProposal(int index) {
+ Assert
+ .isTrue(index >= 0,
+ "Proposal index should never be negative"); //$NON-NLS-1$
+ if (!isValid() || proposals is null || index >= proposals.length) {
+ return;
+ }
+ proposalTable.setSelection(index);
+ proposalTable.showSelection();
+
+ showProposalDescription();
+ }
+
+ /**
+ * Opens this ContentProposalPopup. This method is extended in order to
+ * add the control listener when the popup is opened and to invoke the
+ * secondary popup if applicable.
+ *
+ * @return the return code
+ *
+ * @see dwtx.jface.window.Window#open()
+ */
+ public int open() {
+ int value = super.open();
+ if (popupCloser is null) {
+ popupCloser = new PopupCloserListener();
+ }
+ popupCloser.installListeners();
+ IContentProposal p = getSelectedProposal();
+ if (p !is null) {
+ showProposalDescription();
+ }
+ return value;
+ }
+
+ /**
+ * Closes this popup. This method is extended to remove the control
+ * listener.
+ *
+ * @return true
if the window is (or was already) closed,
+ * and false
if it is still open
+ */
+ public bool close() {
+ popupCloser.removeListeners();
+ if (infoPopup !is null) {
+ infoPopup.close();
+ }
+ bool ret = super.close();
+ notifyPopupClosed();
+ return ret;
+ }
+
+ /*
+ * Show the currently selected proposal's description in a secondary
+ * popup.
+ */
+ private void showProposalDescription() {
+ // If we do not already have a pending update, then
+ // create a thread now that will show the proposal description
+ if (!pendingDescriptionUpdate) {
+ // Create a thread that will sleep for the specified delay
+ // before creating the popup. We do not use Jobs since this
+ // code must be able to run independently of the Eclipse
+ // runtime.
+ auto r = new class() Runnable {
+ public void run() {
+ pendingDescriptionUpdate = true;
+
+ try {
+ Thread.sleep( POPUP_DELAY / 1000.0 );
+ }
+ catch (InterruptedException e) {
+ }
+
+ if (!isValid()) {
+ return;
+ }
+ getShell().getDisplay().syncExec(new class() Runnable {
+ public void run() {
+ // Query the current selection since we have
+ // been delayed
+ IContentProposal p = getSelectedProposal();
+ if (p !is null) {
+ String description = p.getDescription();
+ if (description !is null) {
+ if (infoPopup is null) {
+ infoPopup = new InfoPopupDialog(
+ getShell());
+ infoPopup.open();
+ infoPopup
+ .getShell()
+ .addDisposeListener(
+ new class DisposeListener {
+ public void widgetDisposed(
+ DisposeEvent event) {
+ infoPopup = null;
+ }
+ });
+ }
+ infoPopup.setContents(p
+ .getDescription());
+ } else if (infoPopup !is null) {
+ infoPopup.close();
+ }
+ pendingDescriptionUpdate = false;
+
+ }
+ }
+ });
+ }
+ };
+ Thread t = new Thread(&r.run);
+ t.start();
+ }
+ }
+
+ /*
+ * Accept the current proposal.
+ */
+ private void acceptCurrentProposal() {
+ // Close before accepting the proposal.
+ // This is important so that the cursor position can be
+ // properly restored at acceptance, which does not work without
+ // focus on some controls.
+ // See https://bugs.eclipse.org/bugs/show_bug.cgi?id=127108
+ IContentProposal proposal = getSelectedProposal();
+ close();
+ proposalAccepted(proposal);
+ }
+
+ /*
+ * Request the proposals from the proposal provider, and recompute any
+ * caches. Repopulate the popup if it is open.
+ */
+ private void recomputeProposals(String filterText) {
+ IContentProposal[] allProposals = getProposals();
+ // If the non-filtered proposal list is empty, we should
+ // close the popup.
+ // See https://bugs.eclipse.org/bugs/show_bug.cgi?id=147377
+ if (allProposals.length is 0) {
+ proposals = allProposals;
+ close();
+ } else {
+ // Keep the popup open, but filter by any provided filter text
+ setProposals(filterProposals(allProposals, filterText));
+ }
+ }
+
+ /*
+ * In an async block, request the proposals. This is used when clients
+ * are in the middle of processing an event that affects the widget
+ * content. By using an async, we ensure that the widget content is up
+ * to date with the event.
+ */
+ private void asyncRecomputeProposals(String filterText) {
+ if (isValid()) {
+ control.getDisplay().asyncExec(new class Runnable {
+ String filterText_;
+ this(){filterText_=filterText;}
+ public void run() {
+ recordCursorPosition();
+ recomputeProposals(filterText_);
+ }
+ });
+ } else {
+ recomputeProposals(filterText);
+ }
+ }
+
+ /*
+ * Filter the provided list of content proposals according to the filter
+ * text.
+ */
+ private IContentProposal[] filterProposals(
+ IContentProposal[] proposals, String filterString) {
+ if (filterString.length is 0) {
+ return proposals;
+ }
+
+ // Check each string for a match. Use the string displayed to the
+ // user, not the proposal content.
+ auto list = new ArraySeq!(IContentProposal);
+ for (int i = 0; i < proposals.length; i++) {
+ String string = getString(proposals[i]);
+ if (string.length >= filterString.length
+ && string.substring(0, filterString.length)
+ .equalsIgnoreCase(filterString)) {
+ list.append(proposals[i]);
+ }
+
+ }
+ return list.toArray();
+ }
+
+ Listener getTargetControlListener() {
+ if (targetControlListener is null) {
+ targetControlListener = new TargetControlListener();
+ }
+ return targetControlListener;
+ }
+ }
+
+ /**
+ * Flag that controls the printing of debug info.
+ */
+ public static const bool DEBUG = false;
+
+ /**
+ * Indicates that a chosen proposal should be inserted into the field.
+ */
+ public static const int PROPOSAL_INSERT = 1;
+
+ /**
+ * Indicates that a chosen proposal should replace the entire contents of
+ * the field.
+ */
+ public static const int PROPOSAL_REPLACE = 2;
+
+ /**
+ * Indicates that the contents of the control should not be modified when a
+ * proposal is chosen. This is typically used when a client needs more
+ * specialized behavior when a proposal is chosen. In this case, clients
+ * typically register an IContentProposalListener so that they are notified
+ * when a proposal is chosen.
+ */
+ public static const int PROPOSAL_IGNORE = 3;
+
+ /**
+ * Indicates that there should be no filter applied as keys are typed in the
+ * popup.
+ */
+ public static const int FILTER_NONE = 1;
+
+ /**
+ * Indicates that a single character filter applies as keys are typed in the
+ * popup.
+ */
+ public static const int FILTER_CHARACTER = 2;
+
+ /**
+ * Indicates that a cumulative filter applies as keys are typed in the
+ * popup. That is, each character typed will be added to the filter.
+ */
+ public static const int FILTER_CUMULATIVE = 3;
+
+ /*
+ * Set to true
to use a Table with DWT.VIRTUAL. This is a
+ * workaround for https://bugs.eclipse.org/bugs/show_bug.cgi?id=98585#c40
+ * The corresponding DWT bug is
+ * https://bugs.eclipse.org/bugs/show_bug.cgi?id=90321
+ */
+ private static const bool USE_VIRTUAL = !"motif".equals(DWT.getPlatform()); //$NON-NLS-1$
+
+ /*
+ * The delay before showing a secondary popup.
+ */
+ private static const int POPUP_DELAY = 750;
+
+ /*
+ * The character height hint for the popup. May be overridden by using
+ * setInitialPopupSize.
+ */
+ private static const int POPUP_CHAR_HEIGHT = 10;
+
+ /*
+ * The minimum pixel width for the popup. May be overridden by using
+ * setInitialPopupSize.
+ */
+ private static const int POPUP_MINIMUM_WIDTH = 300;
+
+ /*
+ * The pixel offset of the popup from the bottom corner of the control.
+ */
+ private static const int POPUP_OFFSET = 3;
+
+ /*
+ * Empty string.
+ */
+ private static const String EMPTY = ""; //$NON-NLS-1$
+
+ /*
+ * The object that provides content proposals.
+ */
+ private IContentProposalProvider proposalProvider;
+
+ /*
+ * A label provider used to display proposals in the popup, and to extract
+ * Strings from non-String proposals.
+ */
+ private ILabelProvider labelProvider;
+
+ /*
+ * The control for which content proposals are provided.
+ */
+ private Control control;
+
+ /*
+ * The adapter used to extract the String contents from an arbitrary
+ * control.
+ */
+ private IControlContentAdapter controlContentAdapter;
+
+ /*
+ * The popup used to show proposals.
+ */
+ private ContentProposalPopup popup;
+
+ /*
+ * The keystroke that signifies content proposals should be shown.
+ */
+ private KeyStroke triggerKeyStroke;
+
+ /*
+ * The String containing characters that auto-activate the popup.
+ */
+ private String autoActivateString;
+
+ /*
+ * Integer that indicates how an accepted proposal should affect the
+ * control. One of PROPOSAL_IGNORE, PROPOSAL_INSERT, or PROPOSAL_REPLACE.
+ * Default value is PROPOSAL_INSERT.
+ */
+ private int proposalAcceptanceStyle = PROPOSAL_INSERT;
+
+ /*
+ * A bool that indicates whether key events received while the proposal
+ * popup is open should also be propagated to the control. Default value is
+ * true.
+ */
+ private bool propagateKeys = true;
+
+ /*
+ * Integer that indicates the filtering style. One of FILTER_CHARACTER,
+ * FILTER_CUMULATIVE, FILTER_NONE.
+ */
+ private int filterStyle = FILTER_NONE;
+
+ /*
+ * The listener we install on the control.
+ */
+ private Listener controlListener;
+
+ /*
+ * The list of IContentProposalListener listeners.
+ */
+ private ListenerList proposalListeners;
+
+ /*
+ * The list of IContentProposalListener2 listeners.
+ */
+ private ListenerList proposalListeners2;
+
+ /*
+ * Flag that indicates whether the adapter is enabled. In some cases,
+ * adapters may be installed but depend upon outside state.
+ */
+ private bool isEnabled_ = true;
+
+ /*
+ * The delay in milliseconds used when autoactivating the popup.
+ */
+ private int autoActivationDelay = 0;
+
+ /*
+ * A bool indicating whether a keystroke has been received. Used to see
+ * if an autoactivation delay was interrupted by a keystroke.
+ */
+ private bool receivedKeyDown;
+
+ /*
+ * The desired size in pixels of the proposal popup.
+ */
+ private Point popupSize;
+
+ /*
+ * The remembered position of the insertion position. Not all controls will
+ * restore the insertion position if the proposal popup gets focus, so we
+ * need to remember it.
+ */
+ private int insertionPos = -1;
+
+ /*
+ * A flag that indicates that a pending modify event was caused by
+ * the adapter rather than the user.
+ */
+ private bool modifyingControlContent = false;
+
+ /**
+ * Construct a content proposal adapter that can assist the user with
+ * choosing content for the field.
+ *
+ * @param control
+ * the control for which the adapter is providing content assist.
+ * May not be null
.
+ * @param controlContentAdapter
+ * the IControlContentAdapter
used to obtain and
+ * update the control's contents as proposals are accepted. May
+ * not be null
.
+ * @param proposalProvider
+ * the IContentProposalProvider
used to obtain
+ * content proposals for this control, or null
if
+ * no content proposal is available.
+ * @param keyStroke
+ * the keystroke that will invoke the content proposal popup. If
+ * this value is null
, then proposals will be
+ * activated automatically when any of the auto activation
+ * characters are typed.
+ * @param autoActivationCharacters
+ * An array of characters that trigger auto-activation of content
+ * proposal. If specified, these characters will trigger
+ * auto-activation of the proposal popup, regardless of whether
+ * an explicit invocation keyStroke was specified. If this
+ * parameter is null
, then only a specified
+ * keyStroke will invoke content proposal. If this parameter is
+ * null
and the keyStroke parameter is
+ * null
, then all alphanumeric characters will
+ * auto-activate content proposal.
+ */
+ public this(Control control,
+ IControlContentAdapter controlContentAdapter,
+ IContentProposalProvider proposalProvider, KeyStroke keyStroke,
+ char[] autoActivationCharacters) {
+ proposalListeners = new ListenerList();
+ proposalListeners2 = new ListenerList();
+ //super();
+ // We always assume the control and content adapter are valid.
+ Assert.isNotNull(cast(Object)control);
+ Assert.isNotNull(cast(Object)controlContentAdapter);
+ this.control = control;
+ this.controlContentAdapter = controlContentAdapter;
+
+ // The rest of these may be null
+ this.proposalProvider = proposalProvider;
+ this.triggerKeyStroke = keyStroke;
+ if (autoActivationCharacters.length !is 0 ) {
+ this.autoActivateString = autoActivationCharacters;
+ }
+ addControlListener(control);
+ }
+
+ /**
+ * Get the control on which the content proposal adapter is installed.
+ *
+ * @return the control on which the proposal adapter is installed.
+ */
+ public Control getControl() {
+ return control;
+ }
+
+ /**
+ * Get the label provider that is used to show proposals.
+ *
+ * @return the {@link ILabelProvider} used to show proposals, or
+ * null
if one has not been installed.
+ */
+ public ILabelProvider getLabelProvider() {
+ return labelProvider;
+ }
+
+ /**
+ * Return a bool indicating whether the receiver is enabled.
+ *
+ * @return true
if the adapter is enabled, and
+ * false
if it is not.
+ */
+ public bool isEnabled() {
+ return isEnabled_;
+ }
+
+ /**
+ * Set the label provider that is used to show proposals. The lifecycle of
+ * the specified label provider is not managed by this adapter. Clients must
+ * dispose the label provider when it is no longer needed.
+ *
+ * @param labelProvider
+ * the (@link ILabelProvider} used to show proposals.
+ */
+ public void setLabelProvider(ILabelProvider labelProvider) {
+ this.labelProvider = labelProvider;
+ }
+
+ /**
+ * Return the proposal provider that provides content proposals given the
+ * current content of the field. A value of null
indicates
+ * that there are no content proposals available for the field.
+ *
+ * @return the {@link IContentProposalProvider} used to show proposals. May
+ * be null
.
+ */
+ public IContentProposalProvider getContentProposalProvider() {
+ return proposalProvider;
+ }
+
+ /**
+ * Set the content proposal provider that is used to show proposals.
+ *
+ * @param proposalProvider
+ * the {@link IContentProposalProvider} used to show proposals
+ */
+ public void setContentProposalProvider(
+ IContentProposalProvider proposalProvider) {
+ this.proposalProvider = proposalProvider;
+ }
+
+ /**
+ * Return the array of characters on which the popup is autoactivated.
+ *
+ * @return An array of characters that trigger auto-activation of content
+ * proposal. If specified, these characters will trigger
+ * auto-activation of the proposal popup, regardless of whether an
+ * explicit invocation keyStroke was specified. If this parameter is
+ * null
, then only a specified keyStroke will invoke
+ * content proposal. If this value is null
and the
+ * keyStroke value is null
, then all alphanumeric
+ * characters will auto-activate content proposal.
+ */
+ public char[] getAutoActivationCharacters() {
+ if (autoActivateString is null) {
+ return null;
+ }
+ return autoActivateString/+.toCharArray()+/;
+ }
+
+ /**
+ * Set the array of characters that will trigger autoactivation of the
+ * popup.
+ *
+ * @param autoActivationCharacters
+ * An array of characters that trigger auto-activation of content
+ * proposal. If specified, these characters will trigger
+ * auto-activation of the proposal popup, regardless of whether
+ * an explicit invocation keyStroke was specified. If this
+ * parameter is null
, then only a specified
+ * keyStroke will invoke content proposal. If this parameter is
+ * null
and the keyStroke value is
+ * null
, then all alphanumeric characters will
+ * auto-activate content proposal.
+ *
+ */
+ public void setAutoActivationCharacters(char[] autoActivationCharacters) {
+ if (autoActivationCharacters.length is 0) {
+ this.autoActivateString = null;
+ } else {
+ this.autoActivateString = autoActivationCharacters;
+ }
+ }
+
+ /**
+ * Set the delay, in milliseconds, used before any autoactivation is
+ * triggered.
+ *
+ * @return the time in milliseconds that will pass before a popup is
+ * automatically opened
+ */
+ public int getAutoActivationDelay() {
+ return autoActivationDelay;
+
+ }
+
+ /**
+ * Set the delay, in milliseconds, used before autoactivation is triggered.
+ *
+ * @param delay
+ * the time in milliseconds that will pass before a popup is
+ * automatically opened
+ */
+ public void setAutoActivationDelay(int delay) {
+ autoActivationDelay = delay;
+
+ }
+
+ /**
+ * Get the integer style that indicates how an accepted proposal affects the
+ * control's content.
+ *
+ * @return a constant indicating how an accepted proposal should affect the
+ * control's content. Should be one of PROPOSAL_INSERT
,
+ * PROPOSAL_REPLACE
, or PROPOSAL_IGNORE
.
+ * (Default is PROPOSAL_INSERT
).
+ */
+ public int getProposalAcceptanceStyle() {
+ return proposalAcceptanceStyle;
+ }
+
+ /**
+ * Set the integer style that indicates how an accepted proposal affects the
+ * control's content.
+ *
+ * @param acceptance
+ * a constant indicating how an accepted proposal should affect
+ * the control's content. Should be one of
+ * PROPOSAL_INSERT
, PROPOSAL_REPLACE
,
+ * or PROPOSAL_IGNORE
+ */
+ public void setProposalAcceptanceStyle(int acceptance) {
+ proposalAcceptanceStyle = acceptance;
+ }
+
+ /**
+ * Return the integer style that indicates how keystrokes affect the content
+ * of the proposal popup while it is open.
+ *
+ * @return a constant indicating how keystrokes in the proposal popup affect
+ * filtering of the proposals shown. FILTER_NONE
+ * specifies that no filtering will occur in the content proposal
+ * list as keys are typed. FILTER_CUMULATIVE
+ * specifies that the content of the popup will be filtered by a
+ * string containing all the characters typed since the popup has
+ * been open. FILTER_CHARACTER
specifies the content
+ * of the popup will be filtered by the most recently typed
+ * character. The default is FILTER_NONE
.
+ */
+ public int getFilterStyle() {
+ return filterStyle;
+ }
+
+ /**
+ * Set the integer style that indicates how keystrokes affect the content of
+ * the proposal popup while it is open. Popup-based filtering is useful for
+ * narrowing and navigating the list of proposals provided once the popup is
+ * open. Filtering of the proposals will occur even when the control content
+ * is not affected by user typing. Note that automatic filtering is not used
+ * to achieve content-sensitive filtering such as auto-completion. Filtering
+ * that is sensitive to changes in the control content should be performed
+ * by the supplied {@link IContentProposalProvider}.
+ *
+ * @param filterStyle
+ * a constant indicating how keystrokes received in the proposal
+ * popup affect filtering of the proposals shown.
+ * FILTER_NONE
specifies that no automatic
+ * filtering of the content proposal list will occur as keys are
+ * typed in the popup. FILTER_CUMULATIVE
specifies
+ * that the content of the popup will be filtered by a string
+ * containing all the characters typed since the popup has been
+ * open. FILTER_CHARACTER
specifies that the
+ * content of the popup will be filtered by the most recently
+ * typed character.
+ */
+ public void setFilterStyle(int filterStyle) {
+ this.filterStyle = filterStyle;
+ }
+
+ /**
+ * Return the size, in pixels, of the content proposal popup.
+ *
+ * @return a Point specifying the last width and height, in pixels, of the
+ * content proposal popup.
+ */
+ public Point getPopupSize() {
+ return popupSize;
+ }
+
+ /**
+ * Set the size, in pixels, of the content proposal popup. This size will be
+ * used the next time the content proposal popup is opened.
+ *
+ * @param size
+ * a Point specifying the desired width and height, in pixels, of
+ * the content proposal popup.
+ */
+ public void setPopupSize(Point size) {
+ popupSize = size;
+ }
+
+ /**
+ * Get the bool that indicates whether key events (including
+ * auto-activation characters) received by the content proposal popup should
+ * also be propagated to the adapted control when the proposal popup is
+ * open.
+ *
+ * @return a bool that indicates whether key events (including
+ * auto-activation characters) should be propagated to the adapted
+ * control when the proposal popup is open. Default value is
+ * true
.
+ */
+ public bool getPropagateKeys() {
+ return propagateKeys;
+ }
+
+ /**
+ * Set the bool that indicates whether key events (including
+ * auto-activation characters) received by the content proposal popup should
+ * also be propagated to the adapted control when the proposal popup is
+ * open.
+ *
+ * @param propagateKeys
+ * a bool that indicates whether key events (including
+ * auto-activation characters) should be propagated to the
+ * adapted control when the proposal popup is open.
+ */
+ public void setPropagateKeys(bool propagateKeys) {
+ this.propagateKeys = propagateKeys;
+ }
+
+ /**
+ * Return the content adapter that can get or retrieve the text contents
+ * from the adapter's control. This method is used when a client, such as a
+ * content proposal listener, needs to update the control's contents
+ * manually.
+ *
+ * @return the {@link IControlContentAdapter} which can update the control
+ * text.
+ */
+ public IControlContentAdapter getControlContentAdapter() {
+ return controlContentAdapter;
+ }
+
+ /**
+ * Set the bool flag that determines whether the adapter is enabled.
+ *
+ * @param enabled
+ * true
if the adapter is enabled and responding
+ * to user input, false
if it is ignoring user
+ * input.
+ *
+ */
+ public void setEnabled(bool enabled) {
+ // If we are disabling it while it's proposing content, close the
+ // content proposal popup.
+ if (isEnabled_ && !enabled) {
+ if (popup !is null) {
+ popup.close();
+ }
+ }
+ isEnabled_ = enabled;
+ }
+
+ /**
+ * Add the specified listener to the list of content proposal listeners that
+ * are notified when content proposals are chosen.
+ * null
. If an attempt is made to register
+ * an instance which is already registered with this instance,
+ * this method has no effect.
+ *
+ * @see dwtx.jface.fieldassist.IContentProposalListener
+ */
+ public void addContentProposalListener(IContentProposalListener listener) {
+ proposalListeners.add(cast(Object)listener);
+ }
+
+ /**
+ * Removes the specified listener from the list of content proposal
+ * listeners that are notified when content proposals are chosen.
+ *
null
. If the listener has not already
+ * been registered, this method has no effect.
+ *
+ * @since 3.3
+ * @see dwtx.jface.fieldassist.IContentProposalListener
+ */
+ public void removeContentProposalListener(IContentProposalListener listener) {
+ proposalListeners.remove(cast(Object)listener);
+ }
+
+ /**
+ * Add the specified listener to the list of content proposal listeners that
+ * are notified when a content proposal popup is opened or closed.
+ *
+ *
+ * @param listener
+ * the IContentProposalListener2 to be added as a listener. Must
+ * not be null
. If an attempt is made to register
+ * an instance which is already registered with this instance,
+ * this method has no effect.
+ *
+ * @since 3.3
+ * @see dwtx.jface.fieldassist.IContentProposalListener2
+ */
+ public void addContentProposalListener(IContentProposalListener2 listener) {
+ proposalListeners2.add(cast(Object)listener);
+ }
+
+ /**
+ * Remove the specified listener from the list of content proposal listeners
+ * that are notified when a content proposal popup is opened or closed.
+ *
+ *
+ * @param listener
+ * the IContentProposalListener2 to be removed as a listener.
+ * Must not be null
. If the listener has not
+ * already been registered, this method has no effect.
+ *
+ * @since 3.3
+ * @see dwtx.jface.fieldassist.IContentProposalListener2
+ */
+ public void removeContentProposalListener(IContentProposalListener2 listener) {
+ proposalListeners2.remove(cast(Object)listener);
+ }
+
+ /*
+ * Add our listener to the control. Debug information to be left in until
+ * this support is stable on all platforms.
+ */
+ private void addControlListener(Control control) {
+ if (DEBUG) {
+ Stdout.formatln("ContentProposalListener#installControlListener()"); //$NON-NLS-1$
+ }
+
+ if (controlListener !is null) {
+ return;
+ }
+ controlListener = new class Listener {
+ public void handleEvent(Event e) {
+ if (!isEnabled_) {
+ return;
+ }
+
+ switch (e.type) {
+ case DWT.Traverse:
+ case DWT.KeyDown:
+ if (DEBUG) {
+ StringBuffer sb;
+ if (e.type is DWT.Traverse) {
+ sb = new StringBuffer("Traverse"); //$NON-NLS-1$
+ } else {
+ sb = new StringBuffer("KeyDown"); //$NON-NLS-1$
+ }
+ sb.append(" received by adapter"); //$NON-NLS-1$
+ dump(sb.toString(), e);
+ }
+ // If the popup is open, it gets first shot at the
+ // keystroke and should set the doit flags appropriately.
+ if (popup !is null) {
+ popup.getTargetControlListener().handleEvent(e);
+ if (DEBUG) {
+ StringBuffer sb;
+ if (e.type is DWT.Traverse) {
+ sb = new StringBuffer("Traverse"); //$NON-NLS-1$
+ } else {
+ sb = new StringBuffer("KeyDown"); //$NON-NLS-1$
+ }
+ sb.append(" after being handled by popup"); //$NON-NLS-1$
+ dump(sb.toString(), e);
+ }
+
+ return;
+ }
+
+ // We were only listening to traverse events for the popup
+ if (e.type is DWT.Traverse) {
+ return;
+ }
+
+ // The popup is not open. We are looking at keydown events
+ // for a trigger to open the popup.
+ if (triggerKeyStroke !is null) {
+ // Either there are no modifiers for the trigger and we
+ // check the character field...
+ if ((triggerKeyStroke.getModifierKeys() is KeyStroke.NO_KEY && triggerKeyStroke
+ .getNaturalKey() is e.character)
+ ||
+ // ...or there are modifiers, in which case the
+ // keycode and state must match
+ (triggerKeyStroke.getNaturalKey() is e.keyCode && ((triggerKeyStroke
+ .getModifierKeys() & e.stateMask) is triggerKeyStroke
+ .getModifierKeys()))) {
+ // We never propagate the keystroke for an explicit
+ // keystroke invocation of the popup
+ e.doit = false;
+ openProposalPopup(false);
+ return;
+ }
+ }
+ /*
+ * The triggering keystroke was not invoked. Check for
+ * autoactivation characters.
+ */
+ if (e.character !is 0) {
+ // Auto-activation characters were specified. Check
+ // them.
+ if (autoActivateString !is null) {
+ if (autoActivateString.indexOf(e.character) >= 0) {
+ e.doit = propagateKeys;
+ autoActivate();
+ }
+ } else {
+ // No autoactivation occurred, so record the key
+ // down
+ // as a means to interrupt any autoactivation that
+ // is
+ // pending.
+ receivedKeyDown = true;
+ }
+ }
+ break;
+
+ // See https://bugs.eclipse.org/bugs/show_bug.cgi?id=147377
+ // Given that we will close the popup when there are no valid
+ // proposals, we must reopen it when there are. Normally, the
+ // keydown event handling will catch all the cases where it
+ // should reopen. But when autoactivation should occur on all
+ // content changes, we check it here after keys have been
+ // processed.
+ // See also https://bugs.eclipse.org/bugs/show_bug.cgi?id=183650
+ // We should not autoactivate if the content change was caused
+ // by the popup itself.
+ case DWT.Modify:
+ if (triggerKeyStroke is null && autoActivateString is null
+ && !modifyingControlContent) {
+ if (DEBUG) {
+ dump("Modify event triggers autoactivation", e); //$NON-NLS-1$
+ }
+ autoActivate();
+ }
+ break;
+ default:
+ break;
+ }
+ }
+
+ /**
+ * Dump the given events to "standard" output.
+ *
+ * @param who
+ * who is dumping the event
+ * @param e
+ * the event
+ */
+ private void dump(String who, Event e) {
+ StringBuffer sb = new StringBuffer(
+ "--- [ContentProposalAdapter]\n"); //$NON-NLS-1$
+ sb.append(who);
+ sb.append(Format(" - e: keyCode={}{}", e.keyCode, hex(e.keyCode))); //$NON-NLS-1$
+ sb.append(Format("; character={}{}", e.character, hex(e.character))); //$NON-NLS-1$
+ sb.append(Format("; stateMask={}{}", e.stateMask, hex(e.stateMask))); //$NON-NLS-1$
+ sb.append(Format("; doit={}", e.doit)); //$NON-NLS-1$
+ sb.append(Format("; detail={}", e.detail, hex(e.detail))); //$NON-NLS-1$
+ sb.append(Format("; widget={}", e.widget)); //$NON-NLS-1$
+ Stdout.formatln("{}",sb.toString);
+ }
+
+ private String hex(int i) {
+ return Format("[0x{:X}]", i); //$NON-NLS-1$
+ }
+ };
+ control.addListener(DWT.KeyDown, controlListener);
+ control.addListener(DWT.Traverse, controlListener);
+ control.addListener(DWT.Modify, controlListener);
+
+ if (DEBUG) {
+ Stdout.formatln("ContentProposalAdapter#installControlListener() - installed"); //$NON-NLS-1$
+ }
+ }
+
+ /**
+ * Open the proposal popup and display the proposals provided by the
+ * proposal provider. If there are no proposals to be shown, do not show the
+ * popup. This method returns immediately. That is, it does not wait for the
+ * popup to open or a proposal to be selected.
+ *
+ * @param autoActivated
+ * a bool indicating whether the popup was autoactivated. If
+ * false, a beep will sound when no proposals can be shown.
+ */
+ private void openProposalPopup(bool autoActivated) {
+ if (isValid()) {
+ if (popup is null) {
+ // Check whether there are any proposals to be shown.
+ recordCursorPosition(); // must be done before getting proposals
+ IContentProposal[] proposals = getProposals();
+ if (proposals.length > 0) {
+ if (DEBUG) {
+ Stdout.formatln("POPUP OPENED BY PRECEDING EVENT"); //$NON-NLS-1$
+ }
+ recordCursorPosition();
+ popup = new ContentProposalPopup(null, proposals);
+ popup.open();
+ popup.getShell().addDisposeListener(new class DisposeListener {
+ public void widgetDisposed(DisposeEvent event) {
+ popup = null;
+ }
+ });
+ notifyPopupOpened();
+ } else if (!autoActivated) {
+ getControl().getDisplay().beep();
+ }
+ }
+ }
+ }
+
+ /**
+ * Open the proposal popup and display the proposals provided by the
+ * proposal provider. This method returns immediately. That is, it does not
+ * wait for a proposal to be selected. This method is used by subclasses to
+ * explicitly invoke the opening of the popup. If there are no proposals to
+ * show, the popup will not open and a beep will be sounded.
+ */
+ protected void openProposalPopup() {
+ openProposalPopup(false);
+ }
+
+ /**
+ * Close the proposal popup without accepting a proposal. This method
+ * returns immediately, and has no effect if the proposal popup was not
+ * open. This method is used by subclasses to explicitly close the popup
+ * based on additional logic.
+ *
+ * @since 3.3
+ */
+ protected void closeProposalPopup() {
+ if (popup !is null) {
+ popup.close();
+ }
+ }
+
+ /*
+ * A content proposal has been accepted. Update the control contents
+ * accordingly and notify any listeners.
+ *
+ * @param proposal the accepted proposal
+ */
+ private void proposalAccepted(IContentProposal proposal) {
+ switch (proposalAcceptanceStyle) {
+ case (PROPOSAL_REPLACE):
+ setControlContent(proposal.getContent(), proposal
+ .getCursorPosition());
+ break;
+ case (PROPOSAL_INSERT):
+ insertControlContent(proposal.getContent(), proposal
+ .getCursorPosition());
+ break;
+ default:
+ // do nothing. Typically a listener is installed to handle this in
+ // a custom way.
+ break;
+ }
+
+ // In all cases, notify listeners of an accepted proposal.
+ notifyProposalAccepted(proposal);
+ }
+
+ /*
+ * Set the text content of the control to the specified text, setting the
+ * cursorPosition at the desired location within the new contents.
+ */
+ private void setControlContent(String text, int cursorPosition) {
+ if (isValid()) {
+ // See https://bugs.eclipse.org/bugs/show_bug.cgi?id=183650
+ modifyingControlContent = true;
+
+ controlContentAdapter.setControlContents(control, text,
+ cursorPosition);
+
+ modifyingControlContent = false;
+ }
+ }
+
+ /*
+ * Insert the specified text into the control content, setting the
+ * cursorPosition at the desired location within the new contents.
+ */
+ private void insertControlContent(String text, int cursorPosition) {
+ if (isValid()) {
+ // See https://bugs.eclipse.org/bugs/show_bug.cgi?id=183650
+ modifyingControlContent = true;
+ // Not all controls preserve their selection index when they lose
+ // focus, so we must set it explicitly here to what it was before
+ // the popup opened.
+ // See https://bugs.eclipse.org/bugs/show_bug.cgi?id=127108
+ if (insertionPos !is -1) {
+ controlContentAdapter.setCursorPosition(control, insertionPos);
+ }
+ controlContentAdapter.insertControlContents(control, text,
+ cursorPosition);
+ modifyingControlContent = false;
+ }
+ }
+
+ /*
+ * Check that the control and content adapter are valid.
+ */
+ private bool isValid() {
+ return control !is null && !control.isDisposed()
+ && controlContentAdapter !is null;
+ }
+
+ /*
+ * Record the control's cursor position.
+ */
+ private void recordCursorPosition() {
+ if (isValid()) {
+ insertionPos = getControlContentAdapter()
+ .getCursorPosition(control);
+
+ }
+ }
+
+ /*
+ * Get the proposals from the proposal provider. Gets all of the proposals
+ * without doing any filtering.
+ */
+ private IContentProposal[] getProposals() {
+ if (proposalProvider is null || !isValid()) {
+ return null;
+ }
+ if (DEBUG) {
+ Stdout.formatln(">>> obtaining proposals from provider"); //$NON-NLS-1$
+ }
+ int position = insertionPos;
+ if (position is -1) {
+ position = getControlContentAdapter().getCursorPosition(
+ getControl());
+ }
+ String contents = getControlContentAdapter().getControlContents(
+ getControl());
+ IContentProposal[] proposals = proposalProvider.getProposals(contents,
+ position);
+ return proposals;
+ }
+
+ /**
+ * Autoactivation has been triggered. Open the popup using any specified
+ * delay.
+ */
+ private void autoActivate() {
+ if (autoActivationDelay > 0) {
+ auto r = new class Runnable{
+ public void run(){
+ receivedKeyDown = false;
+ try {
+ Thread.sleep(autoActivationDelay);
+ } catch (InterruptedException e) {
+ }
+ if (!isValid() || receivedKeyDown) {
+ return;
+ }
+ getControl().getDisplay().syncExec(new class Runnable {
+ public void run() {
+ openProposalPopup(true);
+ }
+ });
+ }
+ };
+ Thread t = new Thread(&r.run);
+ t.start();
+ } else {
+ // Since we do not sleep, we must open the popup
+ // in an async exec. This is necessary because
+ // this method may be called in the middle of handling
+ // some event that will cause the cursor position or
+ // other important info to change as a result of this
+ // event occurring.
+ getControl().getDisplay().asyncExec(new class Runnable {
+ public void run() {
+ if (isValid()) {
+ openProposalPopup(true);
+ }
+ }
+ });
+ }
+ }
+
+ /*
+ * A proposal has been accepted. Notify interested listeners.
+ */
+ private void notifyProposalAccepted(IContentProposal proposal) {
+ if (DEBUG) {
+ Stdout.formatln("Notify listeners - proposal accepted."); //$NON-NLS-1$
+ }
+ Object[] listenerArray = proposalListeners.getListeners();
+ for (int i = 0; i < listenerArray.length; i++) {
+ (cast(IContentProposalListener) listenerArray[i])
+ .proposalAccepted(proposal);
+ }
+ }
+
+ /*
+ * The proposal popup has opened. Notify interested listeners.
+ */
+ private void notifyPopupOpened() {
+ if (DEBUG) {
+ Stdout.formatln("Notify listeners - popup opened."); //$NON-NLS-1$
+ }
+ Object[] listenerArray = proposalListeners2.getListeners();
+ for (int i = 0; i < listenerArray.length; i++) {
+ (cast(IContentProposalListener2) listenerArray[i])
+ .proposalPopupOpened(this);
+ }
+ }
+
+ /*
+ * The proposal popup has closed. Notify interested listeners.
+ */
+ private void notifyPopupClosed() {
+ if (DEBUG) {
+ Stdout.formatln("Notify listeners - popup closed."); //$NON-NLS-1$
+ }
+ Object[] listenerArray = proposalListeners2.getListeners();
+ for (int i = 0; i < listenerArray.length; i++) {
+ (cast(IContentProposalListener2) listenerArray[i])
+ .proposalPopupClosed(this);
+ }
+ }
+}
diff -r 50b0163e18f8 -r f12d40e7da8f dwtx/jface/fieldassist/ControlDecoration.d
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/dwtx/jface/fieldassist/ControlDecoration.d Thu Apr 03 18:56:20 2008 +0200
@@ -0,0 +1,1204 @@
+/*******************************************************************************
+ * Copyright (c) 2006, 2007 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
+ * Port to the D programming language:
+ * Frank Benoit + * Decoration images always appear on the left or right side of the field, never + * above or below it. Decorations can be positioned at the top, center, or + * bottom of either side of the control. Future implementations may provide + * additional positioning options for decorations. + *
+ * ControlDecoration renders the image adjacent to the specified (already + * created) control, with no guarantee that it won't be clipped or otherwise + * obscured or overlapped by adjacent controls, including another + * ControlDecoration placed in the same location. Clients should ensure that + * there is adequate space adjacent to the control to show the decoration + * properly. + *
+ * Clients using ControlDecoration should typically ensure that enough margin + * space is reserved for a decoration by altering the layout data margins, + * although this is not assumed or required by the ControlDecoration + * implementation. + *
+ * This class is intended to be instantiated and used by clients. It is not + * intended to be subclassed by clients. + * + * @since 3.3 + * + * @see FieldDecoration + * @see FieldDecorationRegistry + */ +public class ControlDecoration { + /** + * Debug flag for tracing + */ + private static bool DEBUG = false; + + /** + * Cached platform flags for dealing with platform-specific issues. + */ + private static bool CARBON = "carbon".equals(DWT.getPlatform()); //$NON-NLS-1$ + + /** + * The associated control + */ + private Control control; + + /** + * The composite on which to render the decoration and hook mouse events, or + * null if we are hooking all parent composites. + */ + private Composite composite; + + /** + * The associated image. + */ + private Image image; + + /** + * The associated description text. + */ + private String descriptionText; + /** + * The position of the decoration. + */ + private int position; + + /** + * The decoration's visibility flag + */ + private bool visible = true; + + /** + * bool indicating whether the decoration should only be shown when the + * control has focus + */ + private bool showOnlyOnFocus = false; + + /** + * bool indicating whether the decoration should show its description + * text in a hover when the user hovers over the decoration. + */ + private bool showHover = true; + + /** + * Margin width used between the decorator and the control. + */ + private int marginWidth = 0; + + /** + * Registered selection listeners. + */ + private ListenerList selectionListeners; + + /** + * Registered menu detect listeners. + */ + private ListenerList menuDetectListeners; + + /** + * The focus listener + */ + private FocusListener focusListener; + + /** + * The dispose listener + */ + private DisposeListener disposeListener; + + /** + * The paint listener installed for drawing the decoration + */ + private PaintListener paintListener; + + /** + * The mouse listener installed for tracking the hover + */ + private MouseTrackListener mouseTrackListener; + + /** + * The mouse move listener installed for tracking the hover + */ + private MouseMoveListener mouseMoveListener; + + /** + * The untyped listener installed for notifying external listeners + */ + private Listener compositeListener; + + /** + * Control that we last installed a move listener on. We only want one at a + * time. + */ + private Control moveListeningTarget = null; + + /** + * Debug counter used to match add and remove listeners + */ + private int listenerInstalls = 0; + + /** + * The current rectangle used for tracking mouse moves + */ + private Rectangle decorationRectangle; + + /** + * An internal flag tracking whether we have focus. We use this rather than + * isFocusControl() so that we can set the flag as soon as we get the focus + * callback, rather than having to do an asyncExec in the middle of a focus + * callback to ensure that isFocusControl() represents the outcome of the + * event. + */ + private bool hasFocus = false; + + /** + * The hover used for showing description text + */ + private Hover hover; + + /** + * The hover used to show a decoration image's description. + */ + class Hover { + private static const String EMPTY = ""; //$NON-NLS-1$ + + /** + * Offset of info hover arrow from the left or right side. + */ + private int hao = 10; + + /** + * Width of info hover arrow. + */ + private int haw = 8; + + /** + * Height of info hover arrow. + */ + private int hah = 10; + + /** + * Margin around info hover text. + */ + private int hm = 2; + + /** + * This info hover's shell. + */ + Shell hoverShell; + + /** + * The info hover text. + */ + String text = EMPTY; + + /** + * The region used to manage the shell shape + */ + Region region; + + /** + * bool indicating whether the last computed polygon location had an + * arrow on left. (true if left, false if right). + */ + bool arrowOnLeft = true; + + /* + * Create a hover parented by the specified shell. + */ + this(Shell parent) { + Display display = parent.getDisplay(); + hoverShell = new Shell(parent, DWT.NO_TRIM | DWT.ON_TOP + | DWT.NO_FOCUS); + hoverShell.setBackground(display + .getSystemColor(DWT.COLOR_INFO_BACKGROUND)); + hoverShell.setForeground(display + .getSystemColor(DWT.COLOR_INFO_FOREGROUND)); + hoverShell.addPaintListener(new class PaintListener { + public void paintControl(PaintEvent pe) { + pe.gc.drawText(text, hm, hm); + if (!CARBON) { + pe.gc.drawPolygon(getPolygon(true)); + } + } + }); + hoverShell.addMouseListener(new class MouseAdapter { + public void mouseDown(MouseEvent e) { + hideHover(); + } + }); + } + + /* + * Compute a polygon that represents a hover with an arrow pointer. If + * border is true, compute the polygon inset by 1-pixel border. Consult + * the arrowOnLeft flag to determine which side the arrow is on. + */ + int[] getPolygon(bool border) { + Point e = getExtent(); + int b = border ? 1 : 0; + if (arrowOnLeft) { + return [ 0, 0, e.x - b, 0, e.x - b, e.y - b, + hao + haw, e.y - b, hao + haw / 2, e.y + hah - b, hao, + e.y - b, 0, e.y - b, 0, 0 ]; + } + return [ 0, 0, e.x - b, 0, e.x - b, e.y - b, + e.x - hao - b, e.y - b, e.x - hao - haw / 2, e.y + hah - b, + e.x - hao - haw, e.y - b, 0, e.y - b, 0, 0 ]; + } + + /* + * Dispose the hover, it is no longer needed. Dispose any resources + * allocated by the hover. + */ + void dispose() { + if (!hoverShell.isDisposed()) { + hoverShell.dispose(); + } + if (region !is null) { + region.dispose(); + } + } + + /* + * Set the visibility of the hover. + */ + void setVisible(bool visible) { + if (visible) { + if (!hoverShell.isVisible()) { + hoverShell.setVisible(true); + } + } else { + if (hoverShell.isVisible()) { + hoverShell.setVisible(false); + } + } + } + + /* + * Set the text of the hover to the specified text. Recompute the size + * and location of the hover to hover near the decoration rectangle, + * pointing the arrow toward the target control. + */ + void setText(String t, Rectangle decorationRectangle, + Control targetControl) { + if (t is null) { + t = EMPTY; + } + if (!t.equals(text)) { + Point oldSize = getExtent(); + text = t; + hoverShell.redraw(); + Point newSize = getExtent(); + if (!oldSize.opEquals(newSize)) { + // set a flag that indicates the direction of arrow + arrowOnLeft = decorationRectangle.x <= targetControl + .getLocation().x; + setNewShape(); + } + } + + Point extent = getExtent(); + int y = -extent.y - hah + 1; + int x = arrowOnLeft ? -hao + haw / 2 : -extent.x + hao + haw / 2; + + hoverShell.setLocation(control.getParent().toDisplay( + decorationRectangle.x + x, decorationRectangle.y + y)); + } + + /* + * Return whether or not the hover (shell) is visible. + */ + bool isVisible() { + return hoverShell.isVisible(); + } + + /* + * Compute the extent of the hover for the current text. + */ + Point getExtent() { + GC gc = new GC(hoverShell); + Point e = gc.textExtent(text); + gc.dispose(); + e.x += hm * 2; + e.y += hm * 2; + return e; + } + + /* + * Compute a new shape for the hover shell. + */ + void setNewShape() { + Region oldRegion = region; + region = new Region(); + region.add(getPolygon(false)); + hoverShell.setRegion(region); + if (oldRegion !is null) { + oldRegion.dispose(); + } + + } + } + + /** + * Construct a ControlDecoration for decorating the specified control at the + * specified position relative to the control. Render the decoration on top + * of any Control that happens to appear at the specified location. + *
+ * DWT constants are used to specify the position of the decoration relative
+ * to the control. The position should include style bits describing both
+ * the vertical and horizontal orientation. DWT.LEFT
and
+ * DWT.RIGHT
describe the horizontal placement of the
+ * decoration relative to the control, and the constants
+ * DWT.TOP
, DWT.CENTER
, and
+ * DWT.BOTTOM
describe the vertical alignment of the
+ * decoration relative to the control. Decorations always appear on either
+ * the left or right side of the control, never above or below it. For
+ * example, a decoration appearing on the left side of the field, at the
+ * top, is specified as DWT.LEFT | DWT.TOP. If no position style bits are
+ * specified, the control decoration will be positioned to the left and
+ * center of the control (DWT.LEFT | DWT.CENTER
).
+ *
DWT.TOP
,
+ * DWT.BOTTOM
, DWT.LEFT
,
+ * DWT.RIGHT
, and DWT.CENTER
).
+ */
+ public this(Control control, int position) {
+ this(control, position, null);
+
+ }
+
+ /**
+ * Construct a ControlDecoration for decorating the specified control at the
+ * specified position relative to the control. Render the decoration only on
+ * the specified Composite or its children. The decoration will be clipped
+ * if it does not appear within the visible bounds of the composite or its
+ * child composites.
+ *
+ * DWT constants are used to specify the position of the decoration relative
+ * to the control. The position should include style bits describing both
+ * the vertical and horizontal orientation. DWT.LEFT
and
+ * DWT.RIGHT
describe the horizontal placement of the
+ * decoration relative to the control, and the constants
+ * DWT.TOP
, DWT.CENTER
, and
+ * DWT.BOTTOM
describe the vertical alignment of the
+ * decoration relative to the control. Decorations always appear on either
+ * the left or right side of the control, never above or below it. For
+ * example, a decoration appearing on the left side of the field, at the
+ * top, is specified as DWT.LEFT | DWT.TOP. If no position style bits are
+ * specified, the control decoration will be positioned to the left and
+ * center of the control (DWT.LEFT | DWT.CENTER
).
+ *
DWT.TOP
,
+ * DWT.BOTTOM
, DWT.LEFT
,
+ * DWT.RIGHT
, and DWT.CENTER
).
+ * @param composite
+ * The DWT composite within which the decoration should be
+ * rendered. The decoration will be clipped to this composite,
+ * but it may be rendered on a child of the composite. The
+ * decoration will not be visible if the specified composite or
+ * its child composites are not visible in the space relative to
+ * the control, where the decoration is to be rendered. If this
+ * value is null
, then the decoration will be
+ * rendered on whichever composite (or composites) are located in
+ * the specified position.
+ */
+ public this(Control control, int position, Composite composite) {
+ selectionListeners = new ListenerList();
+ menuDetectListeners = new ListenerList();
+ this.position = position;
+ this.control = control;
+ this.composite = composite;
+
+ addControlListeners();
+
+ }
+
+ /**
+ * Adds the listener to the collection of listeners who will be notified
+ * when the platform-specific context menu trigger has occurred, by sending
+ * it one of the messages defined in the MenuDetectListener
+ * interface.
+ *
+ * The widget
field in the SelectionEvent will contain the
+ * Composite on which the decoration is rendered that received the click.
+ * The x
and y
fields will be in coordinates
+ * relative to the display. The data
field will contain the
+ * decoration that received the event.
+ *
SelectionListener
interface.
+ *
+ * widgetSelected
is called when the decoration is selected
+ * (by mouse click). widgetDefaultSelected
is called when the
+ * decoration is double-clicked.
+ *
+ * The widget
field in the SelectionEvent will contain the
+ * Composite on which the decoration is rendered that received the click.
+ * The x
and y
fields will be in coordinates
+ * relative to that widget. The data
field will contain the
+ * decoration that received the event.
+ *
null
+ * if the control has been uninstalled.
+ */
+ public Control getControl() {
+ return control;
+ }
+
+ /**
+ * Add any listeners needed on the target control and on the composite where
+ * the decoration is to be rendered.
+ */
+ private void addControlListeners() {
+ disposeListener = new class DisposeListener {
+ public void widgetDisposed(DisposeEvent event) {
+ dispose();
+ }
+ };
+ printAddListener(control, "DISPOSE"); //$NON-NLS-1$
+ control.addDisposeListener(disposeListener);
+
+ focusListener = new class FocusListener {
+ public void focusGained(FocusEvent event) {
+ hasFocus = true;
+ if (showOnlyOnFocus) {
+ update();
+ }
+ }
+
+ public void focusLost(FocusEvent event) {
+ hasFocus = false;
+ if (showOnlyOnFocus) {
+ update();
+ }
+ }
+ };
+ printAddListener(control, "FOCUS"); //$NON-NLS-1$
+ control.addFocusListener(focusListener);
+
+ // Listener for painting the decoration
+ paintListener = new class PaintListener {
+ public void paintControl(PaintEvent event) {
+ Control control = cast(Control) event.widget;
+ Rectangle rect = getDecorationRectangle(control);
+ if (shouldShowDecoration()) {
+ event.gc.drawImage(getImage(), rect.x, rect.y);
+ }
+ }
+ };
+
+ // Listener for tracking the end of a hover. Only installed
+ // after a hover begins.
+ mouseMoveListener = new class MouseMoveListener {
+ public void mouseMove(MouseEvent event) {
+ if (showHover) {
+ if (!decorationRectangle.contains(event.x, event.y)) {
+ hideHover();
+ // No need to listen any longer
+ printRemoveListener(event.widget, "MOUSEMOVE"); //$NON-NLS-1$
+ (cast(Control) event.widget)
+ .removeMouseMoveListener(mouseMoveListener);
+ moveListeningTarget = null;
+ }
+ }
+ }
+ };
+
+ // Listener for tracking the beginning of a hover. Always installed.
+ mouseTrackListener = new class MouseTrackListener {
+ public void mouseExit(MouseEvent event) {
+ // Just in case we didn't catch it before.
+ Control target = cast(Control) event.widget;
+ if (target is moveListeningTarget) {
+ printRemoveListener(target, "MOUSEMOVE"); //$NON-NLS-1$
+ target.removeMouseMoveListener(mouseMoveListener);
+ moveListeningTarget = null;
+ }
+ hideHover();
+ }
+
+ public void mouseHover(MouseEvent event) {
+ if (showHover) {
+ decorationRectangle = getDecorationRectangle(cast(Control) event.widget);
+ if (decorationRectangle.contains(event.x, event.y)) {
+ showHoverText(getDescriptionText());
+ Control target = cast(Control) event.widget;
+ if (moveListeningTarget is null) {
+ printAddListener(target, "MOUSEMOVE"); //$NON-NLS-1$
+ target.addMouseMoveListener(mouseMoveListener);
+ moveListeningTarget = target;
+ } else if (target !is moveListeningTarget) {
+ printRemoveListener(moveListeningTarget,
+ "MOUSEMOVE"); //$NON-NLS-1$
+ moveListeningTarget
+ .removeMouseMoveListener(mouseMoveListener);
+ printAddListener(target, "MOUSEMOVE"); //$NON-NLS-1$
+ target.addMouseMoveListener(mouseMoveListener);
+ moveListeningTarget = target;
+ } else {
+ // It is already installed on this control.
+ }
+ }
+ }
+ }
+
+ public void mouseEnter(MouseEvent event) {
+ // Nothing to do until a hover occurs.
+ }
+ };
+
+ compositeListener = new class Listener {
+ public void handleEvent(Event event) {
+ // Don't forward events if decoration is not showing
+ if (!visible) {
+ return;
+ }
+ // Notify listeners if any are registered.
+ switch (event.type) {
+ case DWT.MouseDown:
+ if (!selectionListeners.isEmpty())
+ notifySelectionListeners(event);
+ break;
+ case DWT.MouseDoubleClick:
+ if (!selectionListeners.isEmpty())
+ notifySelectionListeners(event);
+ break;
+ case DWT.MenuDetect:
+ if (!menuDetectListeners.isEmpty())
+ notifyMenuDetectListeners(event);
+ break;
+ }
+ }
+ };
+
+ // We do not know which parent in the control hierarchy
+ // is providing the decoration space, so hook all the way up, until
+ // the shell or the specified parent composite is reached.
+ Composite c = control.getParent();
+ while (c !is null) {
+ installCompositeListeners(c);
+ if (composite !is null && composite is c) {
+ // We just installed on the specified composite, so stop.
+ c = null;
+ } else if (cast(Shell)c ) {
+ // We just installed on a shell, so don't go further
+ c = null;
+ } else {
+ c = c.getParent();
+ }
+ }
+ // force a redraw of the decoration area so our paint listener
+ // is notified.
+ update();
+ }
+
+ /*
+ * Install the listeners used to paint and track mouse events on the
+ * composite.
+ */
+ private void installCompositeListeners(Composite c) {
+ if (!c.isDisposed()) {
+ printAddListener(c, "PAINT"); //$NON-NLS-1$
+ c.addPaintListener(paintListener);
+ printAddListener(c, "MOUSETRACK"); //$NON-NLS-1$
+ c.addMouseTrackListener(mouseTrackListener);
+ printAddListener(c, "DWT.MenuDetect"); //$NON-NLS-1$
+ c.addListener(DWT.MenuDetect, compositeListener);
+ printAddListener(c, "DWT.MouseDown"); //$NON-NLS-1$
+ c.addListener(DWT.MouseDown, compositeListener);
+ printAddListener(c, "DWT.MouseDoubleClick"); //$NON-NLS-1$
+ c.addListener(DWT.MouseDoubleClick, compositeListener);
+ }
+ }
+
+ /*
+ * Remove the listeners used to paint and track mouse events on the
+ * composite.
+ */
+ private void removeCompositeListeners(Composite c) {
+ if (!c.isDisposed()) {
+ printRemoveListener(c, "PAINT"); //$NON-NLS-1$
+ c.removePaintListener(paintListener);
+ printRemoveListener(c, "MOUSETRACK"); //$NON-NLS-1$
+ c.removeMouseTrackListener(mouseTrackListener);
+ printRemoveListener(c, "DWT.MenuDetect"); //$NON-NLS-1$
+ c.removeListener(DWT.MenuDetect, compositeListener);
+ printRemoveListener(c, "DWT.MouseDown"); //$NON-NLS-1$
+ c.removeListener(DWT.MouseDown, compositeListener);
+ printRemoveListener(c, "DWT.MouseDoubleClick"); //$NON-NLS-1$
+ c.removeListener(DWT.MouseDoubleClick, compositeListener);
+ }
+ }
+
+ private void notifySelectionListeners(Event event) {
+ if (!(cast(Control)event.widget )) {
+ return;
+ }
+ if (getDecorationRectangle(cast(Control) event.widget).contains(event.x,
+ event.y)) {
+ SelectionEvent clientEvent = new SelectionEvent(event);
+ clientEvent.data = this;
+ if (getImage() !is null) {
+ clientEvent.height = getImage().getBounds().height;
+ clientEvent.width = getImage().getBounds().width;
+ }
+ Object[] listeners;
+ switch (event.type) {
+ case DWT.MouseDoubleClick:
+ if (event.button is 1) {
+ listeners = selectionListeners.getListeners();
+ for (int i = 0; i < listeners.length; i++) {
+ (cast(SelectionListener) listeners[i])
+ .widgetDefaultSelected(clientEvent);
+ }
+ }
+ break;
+ case DWT.MouseDown:
+ if (event.button is 1) {
+ listeners = selectionListeners.getListeners();
+ for (int i = 0; i < listeners.length; i++) {
+ (cast(SelectionListener) listeners[i])
+ .widgetSelected(clientEvent);
+ }
+ }
+ break;
+ }
+ }
+ }
+
+ private void notifyMenuDetectListeners(Event event) {
+ if (getDecorationRectangle(null).contains(event.x, event.y)) {
+ MenuDetectEvent clientEvent = new MenuDetectEvent(event);
+ clientEvent.data = this;
+ Object[] listeners = menuDetectListeners.getListeners();
+ for (int i = 0; i < listeners.length; i++) {
+ (cast(MenuDetectListener) listeners[i]).menuDetected(clientEvent);
+
+ }
+ }
+ }
+
+ /**
+ * Show the specified text using the same hover dialog as is used to show
+ * decorator descriptions. When {@link #setShowHover(bool)} has been set
+ * to true
, a decoration's description text will be shown in
+ * an info hover over the field's control whenever the mouse hovers over the
+ * decoration. This method can be used to show a decoration's description
+ * text at other times (such as when the control receives focus), or to show
+ * other text associated with the field.
+ *
+ * @param text
+ * the text to be shown in the info hover, or null
+ * if no text should be shown.
+ */
+ public void showHoverText(String text) {
+ if (control is null) {
+ return;
+ }
+ showHoverText(text, control);
+ }
+
+ /**
+ * Hide any hover popups that are currently showing on the control. When
+ * {@link #setShowHover(bool)} has been set to true
, a
+ * decoration's description text will be shown in an info hover over the
+ * field's control as long as the mouse hovers over the decoration, and will
+ * be hidden when the mouse exits the decoration. This method can be used to
+ * hide a hover, whether it was shown explicitly using
+ * {@link #showHoverText(String)}, or was showing because the user was
+ * hovering in the decoration.
+ *
+ * This message has no effect if there is no current hover.
+ *
+ */
+ public void hideHover() {
+ if (hover !is null) {
+ hover.setVisible(false);
+ }
+ }
+
+ /**
+ * Show the control decoration. This message has no effect if the decoration
+ * is already showing. If {@link #setShowOnlyOnFocus(bool)} is set to
+ *
+ * Color resources that are returned using methods in this class are maintained
+ * in the JFace color registries, or by DWT. Users of any color resources
+ * provided by this class are not responsible for the lifecycle of the color.
+ * Colors provided by this class should never be disposed by clients. In some
+ * cases, clients are provided information, such as RGB values, in order to
+ * create their own color resources. In these cases, the client should manage
+ * the lifecycle of any created resource.
+ *
+ * @since 3.2
+ * @deprecated As of 3.3, this class is no longer necessary.
+ */
+public class FieldAssistColors {
+
+ private static bool DEBUG = false;
+
+ /*
+ * Keys are background colors, values are the color with the alpha value
+ * applied
+ */
+ private static Map!(Object,Object) requiredFieldColorMap;
+
+ /*
+ * Keys are colors we have created, values are the displays on which they
+ * were created.
+ */
+ private static Map!(Object,Object) displays;
+
+ static this(){
+ requiredFieldColorMap = new HashMap!(Object,Object);
+ displays = new HashMap!(Object,Object);
+ }
+
+ /**
+ * Compute the RGB of the color that should be used for the background of a
+ * control to indicate that the control has an error. Because the color
+ * suitable for indicating an error depends on the colors set into the
+ * control, this color is always computed dynamically and provided as an RGB
+ * value. Clients who use this RGB to create a Color resource are
+ * responsible for managing the life cycle of the color.
+ *
+ * This color is computed dynamically each time that it is queried. Clients
+ * should typically call this method once, create a color from the RGB
+ * provided, and dispose of the color when finished using it.
+ *
+ * @param control
+ * the control for which the background color should be computed.
+ * @return the RGB value indicating a background color appropriate for
+ * indicating an error in the control.
+ */
+ public static RGB computeErrorFieldBackgroundRGB(Control control) {
+ /*
+ * Use a 10% alpha of the error color applied on top of the widget
+ * background color.
+ */
+ Color dest = control.getBackground();
+ Color src = JFaceColors.getErrorText(control.getDisplay());
+ int destRed = dest.getRed();
+ int destGreen = dest.getGreen();
+ int destBlue = dest.getBlue();
+
+ // 10% alpha
+ int alpha = cast(int) (0xFF * 0.10f);
+ // Alpha blending math
+ destRed += (src.getRed() - destRed) * alpha / 0xFF;
+ destGreen += (src.getGreen() - destGreen) * alpha / 0xFF;
+ destBlue += (src.getBlue() - destBlue) * alpha / 0xFF;
+
+ return new RGB(destRed, destGreen, destBlue);
+ }
+
+ /**
+ * Return the color that should be used for the background of a control to
+ * indicate that the control is a required field and does not have content.
+ *
+ * This color is managed by FieldAssistResources and should never be
+ * disposed by clients.
+ *
+ * @param control
+ * the control on which the background color will be used.
+ * @return the color used to indicate that a field is required.
+ */
+ public static Color getRequiredFieldBackgroundColor(Control control) {
+ final Display display = control.getDisplay();
+
+ // If we are in high contrast mode, then don't apply an alpha
+ if (display.getHighContrast()) {
+ return control.getBackground();
+ }
+
+ // See if a color has already been computed
+ Object storedColor = requiredFieldColorMap.get(control.getBackground());
+ if (storedColor !is null) {
+ return cast(Color) storedColor;
+ }
+
+ // There is no color already created, so we must create one.
+ // Use a 15% alpha of yellow on top of the widget background.
+ Color dest = control.getBackground();
+ Color src = display.getSystemColor(DWT.COLOR_YELLOW);
+ int destRed = dest.getRed();
+ int destGreen = dest.getGreen();
+ int destBlue = dest.getBlue();
+
+ // 15% alpha
+ int alpha = cast(int) (0xFF * 0.15f);
+ // Alpha blending math
+ destRed += (src.getRed() - destRed) * alpha / 0xFF;
+ destGreen += (src.getGreen() - destGreen) * alpha / 0xFF;
+ destBlue += (src.getBlue() - destBlue) * alpha / 0xFF;
+
+ // create the color
+ Color color = new Color(display, destRed, destGreen, destBlue);
+ // record the color in a map using the original color as the key
+ requiredFieldColorMap.add(dest, color);
+ // If we have never created a color on this display before, install
+ // a dispose exec on the display.
+ if (!displays.contains(display)) {
+ display.disposeExec(new class Runnable {
+ public void run() {
+ disposeColors(display);
+ }
+ });
+ }
+ // Record the color and its display in a map for later disposal.
+ displays.add(color, display);
+ return color;
+ }
+
+ /*
+ * Dispose any colors that were allocated for the given display.
+ */
+ private static void disposeColors(Display display) {
+ auto toBeRemoved = new ArraySeq!(Object);
+
+ if (DEBUG) {
+ Stdout.formatln("Display map is {}", (cast(Object)displays).toString()); //$NON-NLS-1$
+ Stdout.formatln("Color map is {}", (cast(Object)requiredFieldColorMap).toString()); //$NON-NLS-1$
+ }
+
+ // Look for any stored colors that were created on this display
+ foreach( k, v; displays ){
+ Color color = cast(Color) k;
+ if ((cast(Display) displays.get(color)).opEquals(display)) {
+ // The color is on this display. Mark it for removal.
+ toBeRemoved.append(color);
+
+ // Now look for any references to it in the required field color
+ // map
+ auto toBeRemovedFromRequiredMap = new ArraySeq!(Object);
+ foreach( k, v; requiredFieldColorMap ){
+ Color bgColor = cast(Color) k;
+ if ((cast(Color) requiredFieldColorMap.get(bgColor))
+ .opEquals(color)) {
+ // mark it for removal from the required field color map
+ toBeRemovedFromRequiredMap.append(bgColor);
+ }
+ }
+ // Remove references in the required field map now that
+ // we are done iterating.
+ for (int j = 0; j < toBeRemovedFromRequiredMap.size(); j++) {
+ requiredFieldColorMap.remove(toBeRemovedFromRequiredMap
+ .get(j));
+ }
+ }
+ }
+ // Remove references in the display map now that we are
+ // done iterating
+ for (int i = 0; i < toBeRemoved.size(); i++) {
+ Color color = cast(Color) toBeRemoved.get(i);
+ // Removing from the display map must be done before disposing the
+ // color or else the comparison between this color and the one
+ // in the map will fail.
+ displays.remove(color);
+ // Dispose it
+ if (DEBUG) {
+ Stdout.formatln("Disposing color {}", color.toString()); //$NON-NLS-1$
+ }
+ color.dispose();
+ }
+ if (DEBUG) {
+ Stdout.formatln("Display map is {}", (cast(Object)displays).toString()); //$NON-NLS-1$
+ Stdout.formatln("Color map is {}", (cast(Object)requiredFieldColorMap).toString()); //$NON-NLS-1$
+ }
+ }
+
+}
diff -r 50b0163e18f8 -r f12d40e7da8f dwtx/jface/fieldassist/IContentProposal.d
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/dwtx/jface/fieldassist/IContentProposal.d Thu Apr 03 18:56:20 2008 +0200
@@ -0,0 +1,59 @@
+/*******************************************************************************
+ * Copyright (c) 2005, 2006 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
+ * Port to the D programming language:
+ * Frank Benoit true
, the decoration will only be shown if the control
+ * has focus.
+ */
+ public void show() {
+ if (!visible) {
+ visible = true;
+ update();
+ }
+ }
+
+ /**
+ * Hide the control decoration. This message has no effect if the decoration
+ * is already hidden.
+ */
+ public void hide() {
+ if (visible) {
+ visible = false;
+ update();
+ }
+ }
+
+ /**
+ * Get the description text that may be shown in a hover for this
+ * decoration.
+ *
+ * @return the text to be shown as a description for the decoration, or
+ * null
if none has been set.
+ */
+ public String getDescriptionText() {
+ return descriptionText;
+ }
+
+ /**
+ * Set the image shown in this control decoration. Update the rendered
+ * decoration.
+ *
+ * @param text
+ * the text to be shown as a description for the decoration, or
+ * null
if none has been set.
+ */
+ public void setDescriptionText(String text) {
+ this.descriptionText = text;
+ update();
+ }
+
+ /**
+ * Get the image shown in this control decoration.
+ *
+ * @return the image to be shown adjacent to the control, or
+ * null
if one has not been set.
+ */
+ public Image getImage() {
+ return image;
+ }
+
+ /**
+ * Set the image shown in this control decoration. Update the rendered
+ * decoration.
+ *
+ * @param image
+ * the image to be shown adjacent to the control
+ */
+ public void setImage(Image image) {
+ this.image = image;
+ update();
+ }
+
+ /**
+ * Get the bool that controls whether the decoration is shown only when
+ * the control has focus. The default value of this setting is
+ * false
.
+ *
+ * @return true
if the decoration should only be shown when
+ * the control has focus, and false
if it should
+ * always be shown. Note that if the control is not capable of
+ * receiving focus (DWT.NO_FOCUS
), then the
+ * decoration will never show when this value is true
.
+ */
+ public bool getShowOnlyOnFocus() {
+ return showOnlyOnFocus;
+ }
+
+ /**
+ * Set the bool that controls whether the decoration is shown only when
+ * the control has focus. The default value of this setting is
+ * false
.
+ *
+ * @param showOnlyOnFocus
+ * true
if the decoration should only be shown
+ * when the control has focus, and false
if it
+ * should always be shown. Note that if the control is not
+ * capable of receiving focus (DWT.NO_FOCUS
),
+ * then the decoration will never show when this value is
+ * true
.
+ */
+ public void setShowOnlyOnFocus(bool showOnlyOnFocus) {
+ this.showOnlyOnFocus = showOnlyOnFocus;
+ update();
+ }
+
+ /**
+ * Get the bool that controls whether the decoration's description text
+ * should be shown in a hover when the user hovers over the decoration. The
+ * default value of this setting is true
.
+ *
+ * @return true
if a hover popup containing the decoration's
+ * description text should be shown when the user hovers over the
+ * decoration, and false
if a hover should not be
+ * shown.
+ */
+ public bool getShowHover() {
+ return showHover;
+ }
+
+ /**
+ * Set the bool that controls whether the decoration's description text
+ * should be shown in a hover when the user hovers over the decoration. The
+ * default value of this setting is true
.
+ *
+ * @param showHover
+ * true
if a hover popup containing the
+ * decoration's description text should be shown when the user
+ * hovers over the decoration, and false
if a
+ * hover should not be shown.
+ */
+ public void setShowHover(bool showHover) {
+ this.showHover = showHover;
+ update();
+ }
+
+ /**
+ * Get the margin width in pixels that should be used between the decorator
+ * and the horizontal edge of the control. The default value of this setting
+ * is 0
.
+ *
+ * @return the number of pixels that should be reserved between the
+ * horizontal edge of the control and the adjacent edge of the
+ * decoration.
+ */
+ public int getMarginWidth() {
+ return marginWidth;
+ }
+
+ /**
+ * Set the margin width in pixels that should be used between the decorator
+ * and the horizontal edge of the control. The default value of this setting
+ * is 0
.
+ *
+ * @param marginWidth
+ * the number of pixels that should be reserved between the
+ * horizontal edge of the control and the adjacent edge of the
+ * decoration.
+ */
+ public void setMarginWidth(int marginWidth) {
+ this.marginWidth = marginWidth;
+ update();
+ }
+
+ /**
+ * Something has changed, requiring redraw. Redraw the decoration and update
+ * the hover text if appropriate.
+ */
+ protected void update() {
+ if (control is null || control.isDisposed()) {
+ return;
+ }
+ Rectangle rect = getDecorationRectangle(control.getShell());
+ // Redraw this rectangle in all children
+ control.getShell()
+ .redraw(rect.x, rect.y, rect.width, rect.height, true);
+ control.getShell().update();
+ if (hover !is null && getDescriptionText() !is null) {
+ hover.setText(getDescriptionText(), getDecorationRectangle(control
+ .getParent()), control);
+ }
+ }
+
+ /*
+ * Show the specified text in the hover, positioning the hover near the
+ * specified control.
+ */
+ private void showHoverText(String text, Control hoverNear) {
+ // If we aren't to show a hover, don't do anything.
+ if (!showHover) {
+ return;
+ }
+ // If there is no text, don't do anything.
+ if (text is null) {
+ hideHover();
+ return;
+ }
+
+ // If there is no control, nothing to do
+ if (control is null) {
+ return;
+ }
+ // Create the hover if it's not showing
+ if (hover is null) {
+ hover = new Hover(hoverNear.getShell());
+ }
+ hover.setText(text, getDecorationRectangle(control.getParent()),
+ control);
+ hover.setVisible(true);
+ }
+
+ /*
+ * Remove any listeners installed on the controls.
+ */
+ private void removeControlListeners() {
+ if (control is null) {
+ return;
+ }
+ printRemoveListener(control, "FOCUS"); //$NON-NLS-1$
+ control.removeFocusListener(focusListener);
+ focusListener = null;
+
+ printRemoveListener(control, "DISPOSE"); //$NON-NLS-1$
+ control.removeDisposeListener(disposeListener);
+ disposeListener = null;
+
+ Composite c = control.getParent();
+ while (c !is null) {
+ removeCompositeListeners(c);
+ if (composite !is null && composite is c) {
+ // We previously installed listeners only to the specified
+ // composite, so stop.
+ c = null;
+ } else if (cast(Shell)c ) {
+ // We previously installed listeners only up to the first Shell
+ // encountered, so stop.
+ c = null;
+ } else {
+ c = c.getParent();
+ }
+ }
+ paintListener = null;
+ mouseTrackListener = null;
+ compositeListener = null;
+
+ // We may have a remaining mouse move listener installed
+ if (moveListeningTarget !is null) {
+ printRemoveListener(moveListeningTarget, "MOUSEMOVE"); //$NON-NLS-1$
+ moveListeningTarget.removeMouseMoveListener(mouseMoveListener);
+ moveListeningTarget = null;
+ mouseMoveListener = null;
+ }
+ if (DEBUG) {
+ if (listenerInstalls > 0) {
+ Stdout.formatln("LISTENER LEAK>>>CHECK TRACE ABOVE"); //$NON-NLS-1$
+ } else if (listenerInstalls < 0) {
+ Stdout.formatln("REMOVED UNREGISTERED LISTENERS>>>CHECK TRACE ABOVE"); //$NON-NLS-1$
+ } else {
+ Stdout.formatln("ALL INSTALLED LISTENERS WERE REMOVED."); //$NON-NLS-1$
+ }
+ }
+ }
+
+ /**
+ * Return the rectangle in which the decoration should be rendered, in
+ * coordinates relative to the specified control. If the specified control
+ * is null, return the rectangle in display coordinates.
+ *
+ * @param targetControl
+ * the control whose coordinates should be used
+ * @return the rectangle in which the decoration should be rendered
+ */
+ protected Rectangle getDecorationRectangle(Control targetControl) {
+ if (getImage() is null || control is null) {
+ return new Rectangle(0, 0, 0, 0);
+ }
+ // Compute the bounds first relative to the control's parent.
+ Rectangle imageBounds = getImage().getBounds();
+ Rectangle controlBounds = control.getBounds();
+ int x, y;
+ // Compute x
+ if ((position & DWT.RIGHT) is DWT.RIGHT) {
+ x = controlBounds.x + controlBounds.width + marginWidth;
+ } else {
+ // default is left
+ x = controlBounds.x - imageBounds.width - marginWidth;
+ }
+ // Compute y
+ if ((position & DWT.TOP) is DWT.TOP) {
+ y = controlBounds.y;
+ } else if ((position & DWT.BOTTOM) is DWT.BOTTOM) {
+ y = controlBounds.y + control.getBounds().height
+ - imageBounds.height;
+ } else {
+ // default is center
+ y = controlBounds.y
+ + (control.getBounds().height - imageBounds.height) / 2;
+ }
+
+ // Now convert to coordinates relative to the target control.
+ Point globalPoint = control.getParent().toDisplay(x, y);
+ Point targetPoint;
+ if (targetControl is null) {
+ targetPoint = globalPoint;
+ } else {
+ targetPoint = targetControl.toControl(globalPoint);
+ }
+ return new Rectangle(targetPoint.x, targetPoint.y, imageBounds.width,
+ imageBounds.height);
+ }
+
+ /*
+ * Return true if the decoration should be shown, false if it should not.
+ */
+ private bool shouldShowDecoration() {
+ if (!visible) {
+ return false;
+ }
+ if (control is null || control.isDisposed() || getImage() is null) {
+ return false;
+ }
+
+ if (!control.isVisible()) {
+ return false;
+ }
+ if (showOnlyOnFocus) {
+ return hasFocus;
+ }
+ return true;
+ }
+
+ /*
+ * If in debug mode, print info about adding the specified listener.
+ */
+ private void printAddListener(Widget widget, String listenerType) {
+ listenerInstalls++;
+ if (DEBUG) {
+ Stdout.formatln("Added listener>>>{} to>>>{}", listenerType, widget); //$NON-NLS-1$//$NON-NLS-2$
+ }
+ }
+
+ /*
+ * If in debug mode, print info about adding the specified listener.
+ */
+ private void printRemoveListener(Widget widget, String listenerType) {
+ listenerInstalls--;
+ if (DEBUG) {
+ Stdout.formatln("Removed listener>>>{} from>>>{}", listenerType, widget); //$NON-NLS-1$//$NON-NLS-2$
+ }
+ }
+}
diff -r 50b0163e18f8 -r f12d40e7da8f dwtx/jface/fieldassist/FieldAssistColors.d
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/dwtx/jface/fieldassist/FieldAssistColors.d Thu Apr 03 18:56:20 2008 +0200
@@ -0,0 +1,222 @@
+/*******************************************************************************
+ * Copyright (c) 2006, 2007 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
+ * Port to the D programming language:
+ * Frank Benoit null
, then the content will be displayed as the
+ * label.
+ */
+ public String getLabel();
+
+ /**
+ * Return a description that describes this proposal.
+ *
+ * @return the String label used to further the proposal. If
+ * null
, then no description will be displayed.
+ */
+ public String getDescription();
+
+}
diff -r 50b0163e18f8 -r f12d40e7da8f dwtx/jface/fieldassist/IContentProposalListener.d
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/dwtx/jface/fieldassist/IContentProposalListener.d Thu Apr 03 18:56:20 2008 +0200
@@ -0,0 +1,31 @@
+/*******************************************************************************
+ * Copyright (c) 2005 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
+ * Port to the D programming language:
+ * Frank Benoit null
.
+ *
+ * @param control
+ * the control whose contents are to be set (replaced).
+ * @param contents
+ * the String specifying the new control content.
+ * @param cursorPosition
+ * the zero-based index representing the desired cursor position
+ * in the control's contents after the contents are set.
+ */
+ public void setControlContents(Control control, String contents,
+ int cursorPosition);
+
+ /**
+ * Insert the specified contents into the control's current contents. Must
+ * not be null
.
+ *
+ * @param control
+ * the control whose contents are to be altered.
+ * @param contents
+ * the String to be inserted into the control contents.
+ * @param cursorPosition
+ * the zero-based index representing the desired cursor position
+ * within the inserted contents after the insertion is made.
+ */
+ public void insertControlContents(Control control, String contents,
+ int cursorPosition);
+
+ /**
+ * Get the text contents of the control.
+ *
+ * @param control
+ * the control whose contents are to be retrieved.
+ * @return the String contents of the control.
+ */
+ public String getControlContents(Control control);
+
+ /**
+ * Get the current cursor position in the control. The position is specified
+ * as a zero-based index into the string. Valid ranges are from 0 to N,
+ * where N is the size of the contents string. A value of N indicates that
+ * the cursor is at the end of the contents.
+ *
+ * @param control
+ * the control whose position is to be retrieved.
+ * @return the zero-based index representing the cursor position in the
+ * control's contents.
+ */
+ public int getCursorPosition(Control control);
+
+ /**
+ * Get the bounds (in pixels) of the insertion point for the control
+ * content. This is a rectangle, in coordinates relative to the control,
+ * where the insertion point is displayed. If the implementer does not have
+ * an insertion point, or cannot determine the location of the insertion
+ * point, it is appropriate to return the bounds of the entire control. This
+ * value may be used to position a content proposal popup.
+ *
+ * @param control
+ * the control whose offset is to be retrieved.
+ * @return the pixel width representing the distance between the edge of the
+ * control and the insertion point.
+ */
+ public Rectangle getInsertionBounds(Control control);
+
+ /**
+ * Set the current cursor position in the control. The position is specified
+ * as a zero-based index into the string. Valid ranges are from 0 to N,
+ * where N is the size of the contents string. A value of N indicates that
+ * the cursor is at the end of the contents.
+ *
+ * @param control
+ * the control whose cursor position is to be set.
+ * @param index
+ * the zero-based index representing the cursor position in the
+ * control's contents.
+ */
+ public void setCursorPosition(Control control, int index);
+}
diff -r 50b0163e18f8 -r f12d40e7da8f dwtx/jface/fieldassist/SimpleContentProposalProvider.d
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/dwtx/jface/fieldassist/SimpleContentProposalProvider.d Thu Apr 03 18:56:20 2008 +0200
@@ -0,0 +1,146 @@
+/*******************************************************************************
+ * Copyright (c) 2005, 2006 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
+ * Port to the D programming language:
+ * Frank Benoit true
)
+ * @param position
+ * the current cursor position within the field (ignored)
+ * @return the array of Objects that represent valid proposals for the field
+ * given its current content.
+ */
+ public IContentProposal[] getProposals(String contents, int position) {
+ if (filterProposals) {
+ auto list = new ArraySeq!(IContentProposal);
+ for (int i = 0; i < proposals.length; i++) {
+ if (proposals[i].length > contents.length
+ && proposals[i].substring(0, contents.length)
+ .equalsIgnoreCase(contents)) {
+ list.append(makeContentProposal(proposals[i]));
+ }
+ }
+ return list.toArray();
+ }
+ if (contentProposals is null) {
+ contentProposals = new IContentProposal[proposals.length];
+ for (int i = 0; i < proposals.length; i++) {
+ contentProposals[i] = makeContentProposal(proposals[i]);
+ }
+ }
+ return contentProposals;
+ }
+
+ /**
+ * Set the Strings to be used as content proposals.
+ *
+ * @param items
+ * the array of Strings to be used as proposals.
+ */
+ public void setProposals(String[] items) {
+ this.proposals = items;
+ contentProposals = null;
+ }
+
+ /**
+ * Set the bool that controls whether proposals are filtered according to
+ * the current field content.
+ *
+ * @param filterProposals
+ * true
if the proposals should be filtered to
+ * show only those that match the current contents of the field,
+ * and false
if the proposals should remain the
+ * same, ignoring the field content.
+ * @since 3.3
+ */
+ public void setFiltering(bool filterProposals) {
+ this.filterProposals = filterProposals;
+ // Clear any cached proposals.
+ contentProposals = null;
+ }
+
+ /*
+ * Make an IContentProposal for showing the specified String.
+ */
+ private IContentProposal makeContentProposal( String proposal) {
+ return new class IContentProposal {
+ String proposal_;
+ this(){proposal_=proposal;}
+ public String getContent() {
+ return proposal_;
+ }
+
+ public String getDescription() {
+ return null;
+ }
+
+ public String getLabel() {
+ return null;
+ }
+
+ public int getCursorPosition() {
+ return proposal_.length;
+ }
+ };
+ }
+}
diff -r 50b0163e18f8 -r f12d40e7da8f dwtx/jface/fieldassist/TextContentAdapter.d
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/dwtx/jface/fieldassist/TextContentAdapter.d Thu Apr 03 18:56:20 2008 +0200
@@ -0,0 +1,102 @@
+/*******************************************************************************
+ * Copyright (c) 2005, 2006 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
+ * Port to the D programming language:
+ * Frank Benoit