TextCellEditor with autocomplete in Eclipse SWT/JFace?
Asked Answered
A

2

6

I'd like a TextCellEditor with the standard auto-complete behaviour, that that any user nowadays expects when typing inside an input cell with a list of suggested strings. For a good working example of what I'm after, in Javascript, see this jQuery autocomplete widget.

I couldn't find a good example. I only found (aside from some tiny variations) this TextCellEditorWithContentProposal snippet. But that leaves a lot to be desired:

  • It lists all the words, irrespective of the "partial word" typed in the cell (no partial matching)
  • When the desired word is selected, it is appended to the partial word, instead of replacing it
  • The interaction is ugly and non-intuitive. For example, one would expect the Escape key to tear off the list of suggestions; again, see the Javascript example; here, it also removes the typed letters.

It looks strange to me that such an standard and useful component is not available. Or perhaps it is available? Can someone point to me to a more apt snippet or example?

Addiction answered 10/12, 2017 at 20:48 Comment(0)
U
4

The example you are linking to is a code snippet intended to showcase the API and guide you toward customizing the control to your preference.

Some of your complaints are either invalid or can easily be fixed using public API.

Let's go through them in detail.

All proposals are listed, irrespective of typed text

Note that in the snippet an org.eclipse.jface.fieldassist.SimpleContentProposalProvider is used:

IContentProposalProvider contentProposalProvider = new SimpleContentProposalProvider(new String[] { "red",
                    "green", "blue" });
cellEditor = new TextCellEditorWithContentProposal(viewer.getTable(), contentProposalProvider, null, null);

As suggested in its javadoc it is:

designed to map a static list of Strings to content proposals

To enable a simple filtering of the contents for the snippet, you could call: contentProposalProvider.setFiltering(true);

For anything more complex you will have to replace this with your own implementation of org.eclipse.jface.fieldassist.IContentProposalProvider.

Selection is appended to cell contents, instead of replacing it

The content proposal behavior is defined in the org.eclipse.jface.fieldassist.ContentProposalAdapter. Again a simple method call to org.eclipse.jface.fieldassist.ContentProposalAdapter.setProposalAcceptanceStyle(int) will achieve your target behavior:

contentProposalAdapter = new ContentProposalAdapter(text, new TextContentAdapter(), contentProposalProvider, keyStroke, autoActivationCharacters);
contentProposalAdapter.setProposalAcceptanceStyle(ContentProposalAdapter.PROPOSAL_REPLACE);

Cancelling the proposal should not remove typed content

This is hard to do using just the API, since the ContentProposalAdapter does only propagate the key strokes to the opened ContentProposalPopup without storing them.

You would have to subclass ContentProposalAdapter, in order to have access to ContentProposalAdapter.ContentProposalPopup.filterText.


Most of the functionality in this snippet with sensible defaults can also be obtained in a more simple way by using an org.eclipse.jface.fieldassist.AutoCompleteField.

Underprop answered 13/12, 2017 at 15:33 Comment(0)
H
0

Here is a snippet showing you a simple implementation. You have to customize it but it give you the way.

Note, this is not a generic copy/paste of the documentation or an explanation about the doc.

String[] contentProposals = {"text", "test", "generated"};
// simple content provider based on string array
SimpleContentProposalProvider provider = new SimpleContentProposalProvider(contentProposals);
// enable filtering or disabled it if you are using your own implementation
provider.setFiltering(false);
// content adapter with no keywords and caracters filtering
ContentProposalAdapter adapter = new ContentProposalAdapter(yourcontrolswt, new TextContentAdapter(), provider, null, null);
// you should not replace text content, you will to it bellow
adapter.setProposalAcceptanceStyle(ContentProposalAdapter.PROPOSAL_IGNORE);
// now just put your implementation
adapter.addContentProposalListener(new IContentProposalListener() {

        @Override
         public void proposalAccepted(IContentProposal proposal) {
                if(proposal != null && StringUtils.isNotBlank(proposal.getContent())){
                       // you need filter with blank spaces
                        String contentTextField = getFilterControl().getText();
                         String[] currentWords = getFilterControl().getText().split(" ");
                         StringBuilder textToDisplay = new StringBuilder();
                         if(currentWords.length > 1) {
                                // delete and replace last word
                                String lastWord = currentWords[currentWords.length-1];
                                textToDisplay.append(contentTextField.substring(0, contentTextField.length()-1-lastWord.length()));
                                textToDisplay.append(" ");
                         }
                       // add current proposal to control text content
                       textToDisplay.append(proposal.getContent());
                       yourcontrolswt.setText(textToDisplay.toString());
                }
         }
    });

If you want more you can also have your own content proposal provider If you need a particular object instead of string or something like.

 public class SubstringMatchContentProposalProvider implements IContentProposalProvider {

    private List<String> proposals = Collections.emptyList();

    @Override
    public IContentProposal[] getProposals(String contents, int position) {
        if (position == 0) {
            return null;

        }
        String[] allWords = contents.split(" ");
        // no words available
        if(allWords.length == 0 || StringUtils.isBlank(allWords[allWords.length-1]))
        return null;

        // auto completion on last word found
        String lastWordFound = allWords[allWords.length-1];
        Pattern pattern = Pattern.compile(lastWordFound,
                Pattern.LITERAL | Pattern.CASE_INSENSITIVE /*| Pattern.UNICODE_CASE*/); // this should be not used for better performances
        IContentProposal[] filteredProposals = proposals.stream()
                .filter(proposal -> proposal.length() >= lastWordFound.length() && pattern.matcher(proposal).find())
                .map(ContentProposal::new).toArray(IContentProposal[]::new);

        // no result
        return filteredProposals.length == 0 ? null : filteredProposals;

    }

    public void setProposals(List<String> proposals) {
        this.proposals = proposals;
    }

}
Hornblende answered 17/3, 2020 at 16:11 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.