How to enable content proposal in NatTable TextCellEditor? - eclipse-rcp

I am currently looking for content assist feature in Nattable TextCellEditor.I have found the way to attach the ContentProposalAdapter and IContentProposalProvider by extending the Nattable TextCellEditor. but ,The selected value from the proposed list is not updating in the text control.
Snippet :
#Override
protected Text createEditorControl(final Composite parent, final int Style) {
this.textControl = super.createEditorControl(parent, style);
contentProposalAdapter =
new ContentProposalAdapter(this.textControl, new TextContentAdapter(), contentProposalProvider, keyStroke,
null);
contentProposalAdapter.addContentProposalListener(new IContentProposalListener() {
#Override
public void proposalAccepted(IContentProposal proposal) {
System.out.println(proposal.getContent());
}
});
}

The problem you have is the internal FocusListener that is triggered while selecting a value in the popup. To add the support you also need to override the internal FocusListener with a listener that doesn't fire if the content proposal popup is open.
An example would be to add a boolean flag that indicates that the popup is open and add a listener that sets the flag accordingly.
private boolean popupOpen = false;
...
contentProposalAdapter.addContentProposalListener(new IContentProposalListener2() {
#Override
public void proposalPopupClosed(ContentProposalAdapter adapter) {
this.popupOpen = false;
}
#Override
public void proposalPopupOpened(ContentProposalAdapter adapter) {
this.popupOpen = true;
}
});
And then implement and set a FocusListener in the constructor that takes care of that flag.
this.focusListener = new FocusAdapter() {
#Override
public void focusLost(FocusEvent e) {
if (!TextCellEditor.this.popupOpen) {
if (!commit(MoveDirectionEnum.NONE, true)) {
if (e.widget instanceof Control && !e.widget.isDisposed()) {
((Control) e.widget).forceFocus();
}
} else {
if (!TextCellEditor.this.parent.isDisposed())
TextCellEditor.this.parent.forceFocus();
}
}
}
};
In case the value should be immediately committed after it is selected, you need to add a listener that performs the commit after selection.
contentProposalAdapter.addContentProposalListener(new IContentProposalListener() {
#Override
public void proposalAccepted(IContentProposal proposal) {
commit(MoveDirectionEnum.NONE);
}
});
Unfortunately the AbstractCellEditor#InlineFocusListener is private and can therefore not be extended.
Feel free to file an enhancement ticket for NatTable to introduce the ability to easily add content proposals to a text cell editor.
https://bugs.eclipse.org/bugs/enter_bug.cgi?product=NatTable

Related

How to detect if content assist is open in a SourceViewer

I am using a SourceViewer with a ContentAssistant configured like this:
public class MySourceViewerConfiguration extends TextSourceViewerConfiguration {
#Override
public IContentAssistant getContentAssistant(ISourceViewer sourceViewer) {
ContentAssistant assistant= new ContentAssistant();
...
return assistant;
}
}
SourceViewer sourceViewer = ...
sourceViewer.configure( new MySourceViewer() );
How can I determine if the content assist is currently active, i.e. if the proposal popup window is open?
There seems to be no way to query the SourceViewer directly whether the content assist is active.
However, if a completion listener is installed early enough before the SourceViewer is put into use, it can be used to track assist sessions.
For example:
class ContentAssistListener implements ICompletionListener {
boolean contentAssistActive;
#Override
public void assistSessionStarted( ContentAssistEvent event ) {
contentAssistActive = true;
}
#Override
public void assistSessionEnded( ContentAssistEvent event ) {
contentAssistActive = false;
}
#Override
public void selectionChanged( ICompletionProposal proposal, boolean smartToggle ) {
}
}
The contentAssistActive field will be true if the content assist is currently active and false otherwise.

CellTable click swallowed

I've an combo box which is composed of a text field and a popup with a CellTable showing the suggestion items. The text field has a change handler that updates the CellTable's selection.
When typing a character and clicking an already selected suggestion, the first click is swallowed. The second click works and triggers the selection via the CellTable.addDomHandler(...).
Any idea why first click is swallowed?
Example code:
private static class SuggestFieldTextAndPopupSandbox extends SimplePanel {
private final TextField mText;
private CellTable<Handle<String>> mTable;
private SingleSelectionModel<Handle<String>> mTableSelection;
private SingleSelectionModel<Handle<String>> mSelection;
private ProvidesKey<Handle<String>> mKeyProvider = new SimpleKeyProvider<Handle<String>>();
private PopupPanel mPopup;
private List<Handle<String>> mData;
public SuggestFieldTextAndPopupSandbox() {
mData = Lists.newArrayList(new Handle<String>("AAA"), new Handle<String>("AAB"), new Handle<String>("ABB"));
mSelection = new SingleSelectionModel<Handle<String>>();
mText = new TextField();
mText.addKeyPressHandler(new KeyPressHandler() {
#Override
public void onKeyPress(KeyPressEvent pEvent) {
mPopup.showRelativeTo(mText);
}
});
mText.addBlurHandler(new BlurHandler() {
#Override
public void onBlur(BlurEvent pEvent) {
mTableSelection.setSelected(startsWith(mText.getValue()), true);
}
});
mText.addChangeHandler(new ChangeHandler() {
#Override
public void onChange(ChangeEvent pEvent) {
mText.setText(mText.getText().toUpperCase());
}
});
mTable = new CellTable<Handle<String>>(0, GWT.<TableResources>create(TableResources.class));
mTable.setTableLayoutFixed(false);
mTableSelection = new SingleSelectionModel<Handle<String>>(mKeyProvider);
mTable.setSelectionModel(mTableSelection);
mTable.addDomHandler(new ClickHandler() {
#Override
public void onClick(final ClickEvent pEvent) {
Scheduler.get().scheduleFinally(new ScheduledCommand() {
#Override
public void execute() {
mSelection.setSelected(mTableSelection.getSelectedObject(), true);
mText.setFocus(true);
mPopup.hide();
}
});
}
}, ClickEvent.getType());
mTable.addColumn(new TextColumn<Handle<String>>() {
#Override
public String getValue(Handle<String> pObject) {
return pObject.get();
}
});
mTable.setRowData(mData);
mPopup = new PopupPanel();
mPopup.setAutoHideEnabled(true);
mPopup.setWidget(mTable);
mPopup.setWidth("200px");
mPopup.setHeight("200px");
VerticalPanel p = new VerticalPanel();
p.add(mText);
setWidget(p);
}
private Handle<String> startsWith(final String pValue) {
final String val = nullToEmpty(pValue).toLowerCase();
int i = 0;
for (Handle<String> item : mData) {
String value = item.get();
if (value != null && value.toLowerCase().startsWith(val)) {
return item;
}
i++;
}
return null;
}
}
I reproduced your issue and here is the problem:
when you click on the suggestions the following is happening:
The text field is loosing focus which causes the corresponding ChangeEvent to be dealt with followed by the BlurEvent.
The click causes the popup to get the focus now which is why it is swallowed.
If you remove the ChangeHandler and the BlurHandler of the text field the issue disappears. But I think I found another solution
Try replacing the DOM handler of the mTable with a selection handler relative to the mTableSelection as follows:
mTableSelection.addSelectionChangeHandler(new Handler(){
#Override
public void onSelectionChange(SelectionChangeEvent event) {
Scheduler.get().scheduleFinally(new ScheduledCommand() {
#Override
public void execute() {
mSelection.setSelected(mTableSelection.getSelectedObject(), true);
mText.setFocus(true);
mPopup.hide();
}
});
}
});
Found a way how to properly solve this.
Skipping the blur handler when user hovers the suggestion list area seemed to fix that issue, at least from the tests that were done didn't see any more issues.
This was necessary because just before the user clicks a suggestion item, the text is blurred and it fires a selection change. This in turn cancels the selection made when user clicks an item.

Delete rows from Nattable

I want to implement a row deletion logic in a Nebula Nattable.
This is what I plan to do:
Add context menu to the Nattable which is described in http://blog.vogella.com/2015/02/03/nattable-context-menus-with-eclipse-menus/
Add an SWT Action to the menu which will implement the delete
my question is, which is the best way to accomplish this:
Should I delete the corresponding value from my data model and the table view is refreshed when I execute this.natview.refresh();?
OR
Should I get the rows from SelectionLayer and delete them (if so how do I do ?)?
OR
is there any default support for this function through IConfiguration?
In NatTable you would typically do the following:
Create a command for deleting a row
public class DeleteRowCommand extends AbstractRowCommand {
public DeleteRowCommand(ILayer layer, int rowPosition) {
super(layer, rowPosition);
}
protected DeleteRowCommand(DeleteRowCommand command) {
super(command);
}
#Override
public ILayerCommand cloneCommand() {
return new DeleteRowCommand(this);
}
}
Create a command handler for that command
public class DeleteRowCommandHandler<T> implements ILayerCommandHandler<DeleteRowCommand> {
private List<T> bodyData;
public DeleteRowCommandHandler(List<T> bodyData) {
this.bodyData = bodyData;
}
#Override
public Class<DeleteRowCommand> getCommandClass() {
return DeleteRowCommand.class;
}
#Override
public boolean doCommand(ILayer targetLayer, DeleteRowCommand command) {
//convert the transported position to the target layer
if (command.convertToTargetLayer(targetLayer)) {
//remove the element
this.bodyData.remove(command.getRowPosition());
//fire the event to refresh
targetLayer.fireLayerEvent(new RowDeleteEvent(targetLayer, command.getRowPosition()));
return true;
}
return false;
}
}
Register the command handler to the body DataLayer
bodyDataLayer.registerCommandHandler(
new DeleteRowCommandHandler<your type>(bodyDataProvider.getList()));
Add a menu item to your menu configuration that fires the command
new PopupMenuBuilder(natTable)
.withMenuItemProvider(new IMenuItemProvider() {
#Override
public void addMenuItem(NatTable natTable, Menu popupMenu) {
MenuItem deleteRow = new MenuItem(popupMenu, SWT.PUSH);
deleteRow.setText("Delete");
deleteRow.setEnabled(true);
deleteRow.addSelectionListener(new SelectionAdapter() {
#Override
public void widgetSelected(SelectionEvent event) {
int rowPosition = MenuItemProviders.getNatEventData(event).getRowPosition();
natTable.doCommand(new DeleteRowCommand(natTable, rowPosition));
}
});
}
})
.build();
Using this you don't need to call NatTable#refresh() because the command handler fires a RowDeleteEvent. I also don't suggest to call NatTable#refresh() in such a case, as it might change and refresh more than it should and would not update other states correctly, which is done correctly by firing the RowDeleteEvent.
Note that the shown example deletes the row for which the context menu is opened. If all selected rows should be deleted, you should create a command handler that knows the SelectionLayer and retrieve the selected rows as shown in the other answer.
In our application we do the following:
Get selected row objects:
SelectionLayer selectionLayer = body.getSelectionLayer();
int[] selectedRowPositions = selectionLayer.getFullySelectedRowPositions();
Vector<Your Model Objects> rowObjectsToRemove = new Vector<Your Model Objects>();
for (int rowPosition : selectedRowPositions) {
int rowIndex = selectionLayer.getRowIndexByPosition(rowPosition);
rowObjectsToRemove .add(listDataProvider.getRowObject(rowIndex));
}
Remove them from the data provider
call natTable.refresh()

My selection listener doesn't seem to be registering properly

I am creating an Eclipse RCP application with multiple views. One of my views is a multi-page editor view. Each of those pages has a a master/details block. I need to register all of those TableViewers as selection providers for my other view to listen to.
After much research online, I came across this article about multiple selection providers in a single view. I followed the instructions to create this selection provider for multiple viewers.
class MyMultipleSelectionProvider implements ISelectionProvider {
private final ListenerList selectionListeners = new ListenerList();
private ISelectionProvider delegate;
private final ISelectionChangedListener selectionListener = new ISelectionChangedListener() {
#Override
public void selectionChanged(final SelectionChangedEvent event) {
if (event.getSelectionProvider() == AdaptabilityProfileSelectionProvider.this.delegate) {
fireSelectionChanged( event.getSelection() );
}
}
};
/**
* Sets a new selection provider to delegate to. Selection listeners
* registered with the previous delegate are removed before.
*
* #param newDelegate new selection provider
*/
public void setSelectionProviderDelegate(final ISelectionProvider newDelegate) {
if (this.delegate == newDelegate) {
return;
}
if (this.delegate != null) {
this.delegate.removeSelectionChangedListener(this.selectionListener);
}
this.delegate = newDelegate;
if (newDelegate != null) {
newDelegate.addSelectionChangedListener(this.selectionListener);
fireSelectionChanged(newDelegate.getSelection());
}
}
#Override
public void addSelectionChangedListener(final ISelectionChangedListener listener) {
this.selectionListeners.add(listener);
}
#Override
public ISelection getSelection() {
return this.delegate == null ? null : this.delegate.getSelection();
}
#Override
public void removeSelectionChangedListener(final ISelectionChangedListener listener) {
this.selectionListeners.remove(listener);
}
#Override
public void setSelection(final ISelection selection) {
if (this.delegate != null) {
this.delegate.setSelection(selection);
}
}
protected void fireSelectionChanged(final ISelection selection) {
fireSelectionChanged(this.selectionListeners, selection);
}
private void fireSelectionChanged(final ListenerList list, final ISelection selection) {
final SelectionChangedEvent event = new SelectionChangedEvent(this.delegate, selection);
final Object[] listeners = list.getListeners();
for (int i = 0; i < listeners.length; i++) {
final ISelectionChangedListener listener = (ISelectionChangedListener) listeners[i];
listener.selectionChanged(event);
}
}
}
I added a focusListener on all of the edior's viewers so they become the delegate:
tree.addFocusListener(new FocusAdapter() {
#Override
public void focusGained(final FocusEvent e) {
editor.getSelectionProvider().setSelectionProviderDelegate(MyEditorPage.this.treeViewer);
}
});
And I registered this as the selection provider for my editor:
site.setSelectionProvider( this.selectionProvider );
Then, within my view that needs to hear about the selection, I registered a selection listener for this editor:
getSite().getPage().addSelectionListener(MyEditor.ID, this.selectionListener);
When I run the application, I see that the delegate is being changed and the selection events are being fired. However, the listener list is empty.
I am never calling addSelectionChangeListener() directly. I was under the impression that that was what the selection service is for. Am I wrong? Should I be calling it? If so, when? If not, who is supposed to be adding the listener, and why isn't it happening?
If your code is based on FormEditor (or MultiPageEditorPart) then the selection provider is set to MultiPageSelectionProvider at the end of the init method. This may be overriding your site.setSelectionProvider call.
Using:
#Override
public void init(IEditorSite site, IEditorInput input)
throws PartInitException {
super.init(site, input);
site.setSelectionProvider(this.selectionProvider);
}
should make sure your provider is the one used.

GWT Header CheckBox requires two clicks to fire setValue, after changing its value programatically

I have a GWT DataGrid, and a CheckBox in the Header to select/deselect all rows in the grid.
The code for the CheckBox Header is as follows:
private class CheckboxHeader extends Header<Boolean> implements HasValue<Boolean> {
private boolean checked;
private HandlerManager handlerManager;
/**
* An html string representation of a checked input box.
*/
private final SafeHtml INPUT_CHECKED = SafeHtmlUtils.fromSafeConstant("<input type=\"checkbox\" tabindex=\"-1\" checked/>");
/**
* An html string representation of an unchecked input box.
*/
private final SafeHtml INPUT_UNCHECKED = SafeHtmlUtils.fromSafeConstant("<input type=\"checkbox\" tabindex=\"-1\"/>");
#Override
public void render(Context context, SafeHtmlBuilder sb) {
if (Boolean.TRUE.equals(this.getValue())) {
sb.append(INPUT_CHECKED);
} else {
sb.append(INPUT_UNCHECKED);
}
};
public CheckboxHeader() {
super(new CheckboxCell(true, false));
checked = true;
}
// This method is invoked to pass the value to the CheckboxCell's render method
#Override
public Boolean getValue() {
return checked;
}
#Override
public void onBrowserEvent(Context context, Element elem, NativeEvent nativeEvent) {
int eventType = Event.as(nativeEvent).getTypeInt();
if (eventType == Event.ONCHANGE) {
nativeEvent.preventDefault();
// use value setter to easily fire change event to handlers
setValue(!checked, true);
}
}
#Override
public HandlerRegistration addValueChangeHandler(ValueChangeHandler<Boolean> handler) {
return ensureHandlerManager().addHandler(ValueChangeEvent.getType(), handler);
}
#Override
public void fireEvent(GwtEvent<?> event) {
ensureHandlerManager().fireEvent(event);
}
#Override
public void setValue(Boolean value) {
setValue(value, true);
}
#Override
public void setValue(Boolean value, boolean fireEvents) {
checked = value;
if (fireEvents) {
ValueChangeEvent.fire(this, value);
}
}
private HandlerManager ensureHandlerManager() {
if (handlerManager == null) {
handlerManager = new HandlerManager(this);
}
return handlerManager;
}
}
So, I add the Header to the grid, and I add a ValueChangeHandler to it to do the actual selecting/deselecting of individual CheckBox cells in every row of the grid. This all works.
Every CheckBoxCell has a Field Updater, and on every update it loops through every item in the grid to see if they are all checked, and update the header check box. If at least one is unchecked, the header checkbox will be unchecked. I call setValue() on the header check box, and after that I call redrawHeaders() on the entire grid. This also works.
What doesn't work is - after changing the "state" of the header check box programatically, it takes two clicks for it to fire it's internal setValue again, and therefore trigger my handler. And what's even funnier - the first click does change the state of the check box, but it just doesn't fire the event.
Any help would be appreciated.
How are you constructing the CheckboxCells themselves? I ran into a similar issue with a column of checkboxes "eating" clicks, and the solution was to call CheckboxCell cell = new CheckboxCell(true,true) and then pass that cell into the constructor of the column.