Search code examples
eclipseeclipse-rcptraversaltableviewer

Moving between TableViewers with arrow keys


I am developing an e4 RCP application, using Eclipse 4.2 RC1.

I have a control (Composite) that contains a number of TableViewers. The viewers are arranged vertically. If the user hits the down arrow when the last element of a table is selected, I want the first element of the next table (below) to become selected. Conversely, if the first element is selected and the up arrow is pressed, I want the last element of the previous table (above) to become selected. Because my my column setup, it is impossible to use a single TableViewer. The following function is called in the control's initializer, with all of the viewers passed as arguments:

private void init(IEclipseContext context, IMDecisionTable input, DecisionTableTable...viewers) {
    for (int i = 0; i < viewers.length; i++) {
        viewers[i].hookMenuSupport(context);
        viewers[i].setInput(input);
        viewers[i].getTable().addTraverseListener(new TraverseSelector(
            i > 0 ? viewers[i-1] : null, i < viewers.length - 1 ? viewers[i+1] : null
        ));
    }
}

`TraverseSelector is a nested class of my control:

public class TraverseSelector implements TraverseListener {
    public DecisionTableTable p, n;

    public TraverseSelector(DecisionTableTable previous, DecisionTableTable next) {
        p = previous; n = next;
    }

    @Override public void keyTraversed(TraverseEvent e) {
        if (e.getSource() instanceof Table) {
            Table t = (Table) e.getSource();
            if (e.keyCode == SWT.ARROW_UP && p != null && t.getSelectionIndex() == 0) {
                select(p, -1, true);
                e.detail = SWT.TRAVERSE_NONE;
                e.doit = true;
                p.getTable().setFocus();
            } else if (e.keyCode == SWT.ARROW_DOWN && n != null && t.getSelectionIndex() == t.getItemCount() - 1) {
                select(n, 0, true);
                e.detail = SWT.TRAVERSE_NONE;
                e.doit = true;
                n.getTable().setFocus();
            }
        }
    }
}

When the conditions are met, it attempts to set the selection appropriately. Other relavent code within the class of the control:

private boolean selecting = false;
private ISelection selection = null;
@Override public ISelection getSelection() { return selection; }
@Override public void setSelection(ISelection sel, boolean reveal) {
    if (!selecting) {
        synchronized (this) { selecting = true; }
        selection = sel;
        for (TableViewer viewer : Arrays.asList(ftable, ctable, iatable, catable, pstable))
            viewer.setSelection(selection, reveal);
        synchronized (this) { selecting = false; }
    }
}
@Override public void selectionChanged(SelectionChangedEvent event) {
    if (!selecting) {
        synchronized (this) { selecting = true; }
        selection = event.getSelection();
        for (ISelectionProvider sp : Arrays.asList(ftable, ctable, iatable, catable, pstable))
            if (event.getSelectionProvider() != sp)
                sp.setSelection(null);
        synchronized (this) { selecting = false; }
    }
}
private void select(DecisionTableTable viewer, int index, boolean reveal) {
    Table t = viewer.getTable(); int count = t.getItemCount();
    setSelection(new StructuredSelection(t.getItem((index + count) % count).getData()), reveal);
}

My confusion lies with the two lines e.detail = SWT.TRAVERSE_NONE; and e.doit = true;. If I remove these two statements, things don't work. If I remove those and the setFocus statement, the selection is set to the first or last element of the current table, looping the selection within a single table. detail and doit are 32 and false, respectively, before those statements.


Solution

  • What worked for me was implementing a KeyListener instead of a TransverseListener and adding the line e.doit = false; in the key listener. It seems that some kind of event propagation was causing another selection, and the line above stopped said propagation. This is my final code:

    private boolean selecting;
    private ISelection selection = null;
    @Override public ISelection getSelection() { return selection; }
    @Override public void setSelection(ISelection sel, boolean reveal) {
        if (!selecting) {
            synchronized (this) { selecting = true; }
            selection = sel;
            for (DecisionTableTable viewer : Arrays.asList(ftable, ctable, iatable, catable, pstable))
                viewer.setSelection(selection, reveal);
            synchronized (this) { selecting = false; }
        }
    }
    @Override public void selectionChanged(SelectionChangedEvent event) {
        if (!selecting) {
            synchronized (this) { selecting = true; }
            selection = event.getSelection();
            for (DecisionTableTable viewer : Arrays.asList(ftable, ctable, iatable, catable, pstable))
                if (event.getSelectionProvider() != viewer)
                    viewer.setSelection(selection);
            synchronized (this) { selecting = false; }
        }
    }
    
    public class KeyHandler extends KeyAdapter {
        public DecisionTableTable p, n;
    
        public KeyHandler(DecisionTableTable previous, DecisionTableTable next) {
            p = previous; n = next;
        }
    
        @Override public void keyPressed(KeyEvent e) {
            if (e.getSource() instanceof Table) {
                Table t = (Table) e.getSource();
                if (e.keyCode == SWT.ARROW_UP && p != null && t.getSelectionIndex() == 0) {
                    p.getTable().setFocus();
                    p.setSelection(new StructuredSelection(p.getElementAt(p.getTable().getItemCount() - 1)));
                    e.doit = false;
                } else if (e.keyCode == SWT.ARROW_DOWN && n != null && t.getSelectionIndex() == t.getItemCount() - 1) {
                    n.getTable().setFocus();
                    n.setSelection(new StructuredSelection(n.getElementAt(0)));
                    e.doit = false;
                }
            }
        }
    }