Some Eclipse Foundation services are deprecated, or will be soon. Please ensure you've read this important communication.

Bug 60489

Summary: TabFolders should handle Ctrl+PgUp and Ctrl+PgDown
Product: [Eclipse Project] Platform Reporter: Carolyn MacLeod <carolynmacleod4>
Component: UIAssignee: Stefan Xenos <sxenos>
Status: RESOLVED FIXED QA Contact:
Severity: normal    
Priority: P3 CC: dipalerm, snorthov, Tod_Creasey
Version: 3.0   
Target Milestone: ---   
Hardware: PC   
OS: Windows XP   
Whiteboard:
Attachments:
Description Flags
gifs used in snippet none

Description Carolyn MacLeod CLA 2004-04-29 15:18:39 EDT
200404270800 (This bug has been broken out of bug 10846).

Both Views and Editors are "in" a CTabFolder, however they are not actually 
children of the CTabFolder (they are siblings), so traversal events that go to 
the Views aren't propagated to the CTabFolder. This is a problem with the 
traversal events for PAGE_NEXT and PAGE_PREVIOUS. (Which are sent when the 
user types Ctrl+PgDown or Ctrl+PgUp).

Here is an SWT snippet that shows how these traversal events can be forwarded 
from the View ("tabContents") to the CTabFolder ("tabFolder"). The relevant 
lines of code from the snippet are:

tabContents.addTraverseListener(new TraverseListener() {
	public void keyTraversed(TraverseEvent e) {
		switch (e.detail) {
			case SWT.TRAVERSE_PAGE_NEXT:
			case SWT.TRAVERSE_PAGE_PREVIOUS:
				tabFolder.traverse(e.detail);
				e.detail = SWT.TRAVERSE_NONE;
				e.doit = true;
		}
	}
});

You just have to figure out which controls are "tabContents" and "tabFolder".
The best place to hook the traverse listener is on the fake child(ren) (i.e. 
actually sibling) of the CTabFolder(s) so that they can forward the "page 
next/previous" traversal to the CTabFolder. The line "e.detail = 
SWT.TRAVERSE_NONE" tells the fake child not to do any traversal itself, and 
the line "e.doit = true" says that the traversal has been handled by someone, 
so don't pass it to anybody else.

The snippet emulates eclipse's "tabfolder has siblings that act like children" 
story. While it can be said that this story may have other problems in future 
where additional traversal types should be forwarded, for now I recommend just 
forwarding this one traversal (page next/previous). I also recommend 
checking "instanceof CTabFolder" before forwarding (i.e. be very specific). 
Forwarding other traversals should be done carefully on an as-needed basis.

(I will attach the 7 little gifs that the snippet uses for a tool bar).


import org.eclipse.swt.*;
import org.eclipse.swt.widgets.*;
import org.eclipse.swt.events.*;
import org.eclipse.swt.graphics.*;
import org.eclipse.swt.layout.*;
import org.eclipse.swt.custom.*;

public class EclipseCTabFolderAccessibilityTest {
	static Display display;
	static Shell shell;
	static CTabFolder tabFolder;
	static boolean sibling = true; // whether tab content is child or 
sibling of tab folder
	static Point maxSize;
	static Composite selectedContents;
	
	public static void main(String[] args) {
		display = new Display();
		shell = new Shell(display);
		if (!sibling) shell.setLayout(new GridLayout());
		shell.setText("Eclipse CTabFolder Accessibility Test");
		maxSize = new Point(0, 0);
		tabFolder = new CTabFolder(shell, SWT.BORDER);
		for (int tabNumber = 0; tabNumber < 5; tabNumber++) {
			CTabItem item = new CTabItem(tabFolder, SWT.NULL);
			item.setText("CTab &" + tabNumber);
			Composite tabContents = new Composite(sibling ? 
(Composite) shell : (Composite) tabFolder, SWT.NONE);
			tabContents.setLayout(new GridLayout());
			createTabContents(tabContents, tabNumber);
			if (sibling) {
				tabContents.addTraverseListener(new 
TraverseListener() {
					public void keyTraversed(TraverseEvent 
e) {
						switch (e.detail) {
							case 
SWT.TRAVERSE_PAGE_NEXT:
							case 
SWT.TRAVERSE_PAGE_PREVIOUS:
							
	tabFolder.traverse(e.detail);
								e.detail = 
SWT.TRAVERSE_NONE;
								e.doit = true;
						}
					}
				});
				tabContents.pack();
				Point size = tabContents.getSize();
				if (size.x > maxSize.x || size.y > maxSize.y) 
maxSize = size;
				tabContents.setVisible(false);
				item.setData(tabContents);
			} else {
				item.setControl(tabContents);
			}
		}
		tabFolder.moveBelow(null);
		if (sibling) {
			tabFolder.setSize(tabFolder.computeSize(maxSize.x, 
maxSize.y));
			shell.addControlListener(new ControlAdapter() {
				public void controlResized(ControlEvent e) {
					tabFolder.setBounds(shell.getClientArea
());
					CTabItem item = tabFolder.getSelection
();
					if (item == null) return;
					setItemBounds(item);
				}
			});
			tabFolder.addSelectionListener(new SelectionAdapter() {
				public void widgetSelected(SelectionEvent e) {
					showItem((CTabItem) e.item);
				}
			});
		} else {
			new Button(shell, SWT.PUSH).setText("Hello");
		}
		shell.pack();
		shell.open();
		tabFolder.setSelection(0);
		if (sibling) showItem(tabFolder.getItem(0));
		
		while (!shell.isDisposed()) {
			if (!display.readAndDispatch()) display.sleep();
		}
	}
	
	static void setItemBounds(CTabItem item) {
		Composite tabContents = (Composite) item.getData();
		tabContents.setBounds(tabFolder.getClientArea());
	}

	static void showItem(CTabItem item) {
		Composite tabContents = (Composite) item.getData();
		tabContents.setBounds(tabFolder.getClientArea());
		if (selectedContents != null) selectedContents.setVisible
(false);
		tabContents.setVisible(true);
		selectedContents = tabContents;
		tabContents.setFocus();
	}

	static void createTabContents(Composite tabContents, int tabNumber) {
		Label itemLabel = new Label(tabContents, SWT.NONE);
		itemLabel.setText("Label for CTabItem " + tabNumber + " is a 
child of a composite");
		switch (tabNumber) {
			case 0:
				Text itemText = new Text(tabContents, 
SWT.MULTI | SWT.BORDER);
				itemText.setLayoutData(new GridData
(GridData.FILL_BOTH));
				itemText.setText("\nText for CTabItem " + 
tabNumber + "\n\n\n");
				break;
			case 1:
				Button itemCheck = new Button(tabContents, 
SWT.CHECK);
				itemCheck.setLayoutData(new GridData
(GridData.FILL_HORIZONTAL));
				itemCheck.setText("Checkbox for CTabItem " + 
tabNumber);
				break;
			case 2:
				Tree itemTree = new Tree(tabContents, 
SWT.SINGLE);
				itemTree.setLayoutData(new GridData
(GridData.FILL_BOTH));
				for (int i = 0; i < 4; i++) {
					TreeItem treeItem = new TreeItem 
(itemTree, SWT.NULL);
					treeItem.setText ("TreeItem " + i + " 
for CTabItem " + tabNumber);
					for (int j = 0; j < 3; j++) {
						new TreeItem(treeItem, 
SWT.NONE).setText("TreeItem " + i + j + " for CTabItem " + tabNumber);
					}
				}
				break;
			case 3:
				Table itemTable = new Table(tabContents, 
SWT.SINGLE);
				itemTable.setLayoutData(new GridData
(GridData.FILL_BOTH));
				itemTable.setHeaderVisible(true);
				itemTable.setLinesVisible(true);
				for (int col = 0; col < 2; col++) {
					TableColumn column = new TableColumn
(itemTable, SWT.NONE);
					column.setText("Column " + col);
				}
				for (int i = 0; i < 4; i++) {
					TableItem tableItem = new TableItem 
(itemTable, SWT.NULL);
					tableItem.setText (new String [] 
{"TableItem " + i, "for CTabItem " + tabNumber});
				}
				for (int col = 0; col < 2; col++) {
					itemTable.getColumn(col).pack();
				}
				break;
			case 4:
				ToolBar itemToolBar = new ToolBar(tabContents, 
SWT.FLAT);
				itemToolBar.setLayoutData(new GridData
(GridData.FILL_HORIZONTAL));
		        String[] toolBarFileNames = new String [] 
{"save", "saveas", "printer", "debug", "run", "search", "opentype"};
				for (int tool = 0; tool < 
toolBarFileNames.length; tool++) {
					String fileName = toolBarFileNames
[tool];
		           	ToolItem item = new ToolItem(itemToolBar, 
SWT.PUSH);
		            item.setImage(createToolBarIcon(display, 
fileName));
		            item.setToolTipText(fileName + " ToolItem for 
CTabItem " + tabNumber);
				}
				break;
		}
	}

	static Image createToolBarIcon(Display display, String fileName) {
		try {
			ImageData source = new ImageData
(AccessibleToolBarTest.class.getResourceAsStream(fileName + ".gif"));
			ImageData mask = source.getTransparencyMask();
			return new Image(display, source, mask);
		} catch (Exception e) {
		}
		return null;
	}
}
Comment 1 Carolyn MacLeod CLA 2004-04-29 15:20:43 EDT
Created attachment 10127 [details]
gifs used in snippet
Comment 2 Carolyn MacLeod CLA 2004-05-04 17:44:16 EDT
See also:
https://bugs.eclipse.org/bugs/show_bug.cgi?id=22004
https://bugs.eclipse.org/bugs/show_bug.cgi?id=15779

Apparently, this used to work... so what we have here is a regression. It has 
been broken, and fixed, several times. I think it should have higher priority. 
If 3.0 goes out without Ctrl+PgUp/Ctrl+PgDn I suspect there will be several 
people who say we are not accessible. Seriously consider raising the 
priority... Tod?
Comment 3 Stefan Xenos CLA 2004-05-12 21:04:49 EDT
Fixed in head.

Note: the patch does not check instanceof CTabFolder, since this will only work
with the default presentation. We also do not modify e.detail, since other
traverse listeners on the part's control should still get the event (but we
don't want it to continue traversing to the parent).

I tested the behavior of e.doit by attaching a traverse listener to a parent and
child control, and inserting System.out.printlns. It seems to have the opposite
semantics than what was described in this original PR, so I have set e.doit to
false rather than true. Please let me know if this is incorrect.
Comment 4 Carolyn MacLeod CLA 2004-05-13 02:59:28 EDT
Try adding a key listener. If you don't eat the key (sequence), then it will 
go to the control that had focus. Steve, is my understanding of how traverse 
works correct? Shouldn't Stefan be saying doit = true when the 'repositioned 
sibling' of the tab folder (aka 'fake child of tab folder') gets and forwards 
a page traversal to the tab folder?
Comment 5 Steve Northover CLA 2004-05-13 09:21:39 EDT
If you perform the action, you need to stop the event by setting the detail.  
Otherwise, another listener can run and another action might be performed 
causing two actions for one key stroke.  If you do not set doit=true, then the 
keystroke will be delivered to the control, rather than traversing.  This can 
also cause another action to happen if the keystroke is used by the control.  
In the case of Ctrl+PgUp, this key is used by Text editors to move the caret 
to the bottom of the page.  If focus is in a Text editor, it will both 
traverse (because you called traverse()) and move the caret.
Comment 6 Steve Northover CLA 2004-05-13 09:22:22 EDT
Did I say top of the page?  I meant bottom.