Thursday, June 19, 2014

TableViewer context menus

Recently I played around with context menus on tableviewers. I wanted to track the column where the context menu was activated. As it turned out to be quite a tricky task (until you know how it is done) I would like to share my findings. Furthermore I would like to show how to attach context menus to editors without having default entries for Run As and similar actions.

Source code for this tutorial is available on googlecode as a single zip archive, as a Team Project Set or you can checkout the SVN projects directly.

Preparations

For this tutorial I created a Plug-in Project with a simple editor extension. The editor contains a TableViewer with some sample columns. Nothing special about that. Have a look at the source code or Lars' excellent editor tutorial if you are not familiar with these steps.

Step 1: Creating the editor context menu

To enable context menus, we need to register them in our editor code. The typical snippet to do this looks as follows:
MenuManager menuManager = new MenuManager();
Menu contextMenu = menuManager.createContextMenu(table);
table.setMenu(contextMenu);
getSite().registerContextMenu(menuManager, tableViewer);
registerContextMenu() automatically searches for a menu contribution with locationURI = popup:<part.id>. In our case it would look for popup:com.codeandme.editor.sample. You may also provide a dedicated id when registering the context menu. Just make sure you provide the id without the "popup:" prefix, while the menu contribution needs the "popup:" prefix in its locationURI.

You will end up with a context menu already populated with some default entries like Run As, Compare With, Team, and some more. To get rid of them, we need to register the menu using a different site:
getEditorSite().registerContextMenu(menuManager, tableViewer, false);
The boolean parameter allows to enable/disable context menu entries for the editor input part.


Step 2: Track the active column

When we want our context menu entry to behave differently depending on the column from where it was triggered, we need to track columns. Some solutions on the web use MouseListeners, which work well for the table body, but not for the header row. A nicer solution relies on MenuDetect events:
fTableViewer.getTable().addListener(SWT.MenuDetect, this);

@Override
public void handleEvent(Event event) {
 Table table = fTableViewer.getTable();

 // calculate click offset within table area
 Point point = Display.getDefault().map(null, table, new Point(event.x, event.y));
 Rectangle clientArea = table.getClientArea();
 fHeaderArea = (clientArea.y <= point.y) && (point.y < (clientArea.y + table.getHeaderHeight()));

 ViewerCell cell = fTableViewer.getCell(point);
 if (cell != null)
  fSelectedColumnIndex = cell.getColumnIndex();

 else {
  // no cell detected, click on header
  int xOffset = point.x;
  int columnIndex = 0;
  int[] order = table.getColumnOrder();
  while ((columnIndex < table.getColumnCount()) && (xOffset > table.getColumn(order[columnIndex]).getWidth())) {
   xOffset -= table.getColumn(order[columnIndex]).getWidth();
   columnIndex++;
  }

  fSelectedColumnIndex = (columnIndex < table.getColumnCount()) ? order[columnIndex] : NO_COLUMN;
 }
}
The full helper class is available under EPL and can be downloaded from the source repository.

Step 2: Delete the active column

To provide a usage example for the TableColumnTracker we will extend our editor and allow users to delete the column under the cursor using the context menu.

The command implementation simply asks the current editor to do the job:
@Override
public Object execute(ExecutionEvent event) throws ExecutionException {
 IWorkbenchPart part = HandlerUtil.getActivePart(event);
 if (part instanceof SampleEditor)
  ((SampleEditor) part).deleteColumn();

 return null;
}
The editor needs to install the tracker and dispose the selected column upon request:
public class SampleEditor extends EditorPart {

 private TableColumnTracker fColumnTracker;

 @Override
 public void createPartControl(Composite parent) {

  [...]

  MenuManager menuManager = new MenuManager();
  Menu contextMenu = menuManager.createContextMenu(table);
  table.setMenu(contextMenu);
  getEditorSite().registerContextMenu(menuManager, fTableViewer, false);

  fColumnTracker = new TableColumnTracker(fTableViewer);
 }

 public void deleteColumn() {
  int columnIndex = fColumnTracker.getSelectedColumnIndex();

  if (columnIndex != TableColumnTracker.NO_COLUMN) {
   fTableViewer.getTable().getColumn(columnIndex).dispose();
   fTableViewer.refresh();
  }
 }
}

No comments:

Post a Comment