blob: 2a4aa9277ba7973356dae77a309e5517f8987e02 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2000, 2005 IBM Corporation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* IBM Corporation - initial API and implementation
*******************************************************************************/
package org.eclipse.ui.internal;
import org.eclipse.core.commands.Command;
import org.eclipse.core.commands.ParameterizedCommand;
import org.eclipse.jface.action.Action;
import org.eclipse.jface.bindings.Trigger;
import org.eclipse.jface.bindings.TriggerSequence;
import org.eclipse.jface.bindings.keys.KeyStroke;
import org.eclipse.jface.bindings.keys.SWTKeySupport;
import org.eclipse.jface.preference.IPreferenceStore;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.FocusEvent;
import org.eclipse.swt.events.FocusListener;
import org.eclipse.swt.events.HelpEvent;
import org.eclipse.swt.events.HelpListener;
import org.eclipse.swt.events.KeyEvent;
import org.eclipse.swt.events.KeyListener;
import org.eclipse.swt.events.MouseEvent;
import org.eclipse.swt.events.MouseListener;
import org.eclipse.swt.events.TraverseEvent;
import org.eclipse.swt.events.TraverseListener;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.layout.FillLayout;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.Table;
import org.eclipse.swt.widgets.TableColumn;
import org.eclipse.swt.widgets.TableItem;
import org.eclipse.ui.IEditorReference;
import org.eclipse.ui.IWorkbenchPage;
import org.eclipse.ui.IWorkbenchPart;
import org.eclipse.ui.IWorkbenchPartReference;
import org.eclipse.ui.IWorkbenchWindow;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.commands.ICommandService;
import org.eclipse.ui.contexts.IContextService;
import org.eclipse.ui.keys.IBindingService;
/**
* Implements a action to enable the user switch between parts using keyboard.
*/
public class CyclePartAction extends PageEventAction {
/**
* The list of key bindings for the backward command when it is open. This
* value is <code>null</code> if the dialog is not open.
*/
private TriggerSequence[] backwardTriggerSequences = null;
private ParameterizedCommand commandBackward = null;
private ParameterizedCommand commandForward = null;
protected boolean forward;
/**
* The list of key bindings for the forward command when it is open. This
* value is <code>null</code> if the dialog is not open.
*/
private TriggerSequence[] forwardTriggerSequences = null;
private Object selection;
/**
* Creates a CyclePartAction.
*
* @param window
* The workbench window on which the dialog should be created.
* @param next
* Whether the action is to move to the next part (as opposed to
* the previous).
*/
public CyclePartAction(IWorkbenchWindow window, boolean next) {
super("", window); //$NON-NLS-1$
forward = next;
setText();
updateState();
}
/**
* Activate the selected item.
*
* @param page
* the page
* @param selectedItem
* the selected item
*/
public void activate(IWorkbenchPage page, Object selectedItem) {
if (selectedItem != null) {
if (selectedItem instanceof IEditorReference) {
page.setEditorAreaVisible(true);
}
IWorkbenchPart part = ((IWorkbenchPartReference) selectedItem)
.getPart(true);
if (part != null) {
page.activate(part);
}
}
}
/**
* Add all views to the dialog in the activation order
*/
protected void addItems(Table table, WorkbenchPage page) {
IWorkbenchPartReference refs[] = page.getSortedParts();
boolean includeEditor = true;
for (int i = refs.length - 1; i >= 0; i--) {
if (refs[i] instanceof IEditorReference) {
if (includeEditor) {
IEditorReference activeEditor = (IEditorReference) refs[i];
TableItem item = new TableItem(table, SWT.NONE);
item.setText(WorkbenchMessages.CyclePartAction_editor);
item.setImage(activeEditor.getTitleImage());
item.setData(activeEditor);
includeEditor = false;
}
} else {
TableItem item = new TableItem(table, SWT.NONE);
item.setText(refs[i].getTitle());
item.setImage(refs[i].getTitleImage());
item.setData(refs[i]);
}
}
}
private void addKeyListener(final Table table, final Shell dialog) {
table.addKeyListener(new KeyListener() {
private boolean firstKey = true;
private boolean quickReleaseMode = false;
public void keyPressed(KeyEvent e) {
int keyCode = e.keyCode;
char character = e.character;
int accelerator = SWTKeySupport
.convertEventToUnmodifiedAccelerator(e);
KeyStroke keyStroke = SWTKeySupport
.convertAcceleratorToKeyStroke(accelerator);
// System.out.println("\nPRESSED");
// printKeyEvent(e);
// System.out.println("accelerat:\t" + accelerator + "\t (" +
// KeySupport.formatStroke(Stroke.create(accelerator), true) +
// ")");
boolean acceleratorForward = false;
boolean acceleratorBackward = false;
if (commandForward != null) {
if (forwardTriggerSequences != null) {
final int forwardCount = forwardTriggerSequences.length;
for (int i = 0; i < forwardCount; i++) {
final TriggerSequence triggerSequence = forwardTriggerSequences[i];
// Compare the last key stroke of the binding.
final Trigger[] triggers = triggerSequence
.getTriggers();
final int triggersLength = triggers.length;
if ((triggersLength > 0)
&& (triggers[triggersLength - 1]
.equals(keyStroke))) {
acceleratorForward = true;
break;
}
}
}
}
if (commandBackward != null) {
if (backwardTriggerSequences != null) {
final int backwardCount = backwardTriggerSequences.length;
for (int i = 0; i < backwardCount; i++) {
final TriggerSequence triggerSequence = backwardTriggerSequences[i];
// Compare the last key stroke of the binding.
final Trigger[] triggers = triggerSequence
.getTriggers();
final int triggersLength = triggers.length;
if ((triggersLength > 0)
&& (triggers[triggersLength - 1]
.equals(keyStroke))) {
acceleratorBackward = true;
break;
}
}
}
}
if (character == SWT.CR || character == SWT.LF) {
ok(dialog, table);
} else if (acceleratorForward) {
if (firstKey && e.stateMask != 0) {
quickReleaseMode = true;
}
int index = table.getSelectionIndex();
table.setSelection((index + 1) % table.getItemCount());
} else if (acceleratorBackward) {
if (firstKey && e.stateMask != 0) {
quickReleaseMode = true;
}
int index = table.getSelectionIndex();
table.setSelection(index >= 1 ? index - 1 : table
.getItemCount() - 1);
} else if (keyCode != SWT.ALT && keyCode != SWT.COMMAND
&& keyCode != SWT.CTRL && keyCode != SWT.SHIFT
&& keyCode != SWT.ARROW_DOWN && keyCode != SWT.ARROW_UP
&& keyCode != SWT.ARROW_LEFT
&& keyCode != SWT.ARROW_RIGHT) {
cancel(dialog);
}
firstKey = false;
}
public void keyReleased(KeyEvent e) {
int keyCode = e.keyCode;
int stateMask = e.stateMask;
// char character = e.character;
// int accelerator = stateMask | (keyCode != 0 ? keyCode :
// convertCharacter(character));
// System.out.println("\nRELEASED");
// printKeyEvent(e);
// System.out.println("accelerat:\t" + accelerator + "\t (" +
// KeySupport.formatStroke(Stroke.create(accelerator), true) +
// ")");
final IPreferenceStore store = WorkbenchPlugin.getDefault()
.getPreferenceStore();
final boolean stickyCycle = store
.getBoolean(IPreferenceConstants.STICKY_CYCLE);
if ((!stickyCycle && (firstKey || quickReleaseMode))
&& keyCode == stateMask) {
ok(dialog, table);
}
}
});
}
/*
* Add mouse listener to the table closing it when the mouse is pressed.
*/
private void addMouseListener(final Table table, final Shell dialog) {
table.addMouseListener(new MouseListener() {
public void mouseDoubleClick(MouseEvent e) {
ok(dialog, table);
}
public void mouseDown(MouseEvent e) {
ok(dialog, table);
}
public void mouseUp(MouseEvent e) {
ok(dialog, table);
}
});
}
/**
* Adds a listener to the given table that blocks all traversal operations.
*
* @param table
* The table to which the traversal suppression should be added;
* must not be <code>null</code>.
*/
private final void addTraverseListener(final Table table) {
table.addTraverseListener(new TraverseListener() {
/**
* Blocks all key traversal events.
*
* @param event
* The trigger event; must not be <code>null</code>.
*/
public final void keyTraversed(final TraverseEvent event) {
event.doit = false;
}
});
}
/*
* Close the dialog and set selection to null.
*/
private void cancel(Shell dialog) {
selection = null;
dialog.close();
}
/**
* Return the backward action.
*
* @return the backward action
*/
public String getBackwardActionDefinitionId() {
return commandBackward.getCommand().getId();
}
/**
* Return the forward action.
*
* @return the forward action
*/
public String getForwardActionDefinitionId() {
return commandForward.getCommand().getId();
}
/**
* Returns the string which will be shown in the table header.
*/
protected String getTableHeader() {
return WorkbenchMessages.CyclePartAction_header;
}
// private static void printKeyEvent(KeyEvent keyEvent) {
// System.out.println("keyCode:\t" + keyEvent.keyCode + "\t (" +
// KeySupport.formatStroke(Stroke.create(keyEvent.keyCode), true) + ")");
// System.out.println("stateMask:\t" + keyEvent.stateMask + "\t (" +
// KeySupport.formatStroke(Stroke.create(keyEvent.stateMask), true) + ")");
// System.out.println("character:\t" + (int) keyEvent.character + "\t (" +
// keyEvent.character + ")");
// }
/*
* Close the dialog saving the selection
*/
private void ok(Shell dialog, final Table table) {
TableItem[] items = table.getSelection();
if (items != null && items.length == 1) {
selection = items[0].getData();
}
dialog.close();
}
/*
* Open a dialog showing all views in the activation order
*/
private void openDialog(WorkbenchPage page) {
final int MAX_ITEMS = 22;
selection = null;
final Shell dialog = new Shell(getWorkbenchWindow().getShell(),
SWT.MODELESS);
Display display = dialog.getDisplay();
dialog.setLayout(new FillLayout());
final Table table = new Table(dialog, SWT.SINGLE | SWT.FULL_SELECTION);
table.setHeaderVisible(true);
table.setLinesVisible(true);
TableColumn tc = new TableColumn(table, SWT.NONE);
tc.setResizable(false);
tc.setText(getTableHeader());
addItems(table, page);
int tableItemCount = table.getItemCount();
switch (tableItemCount) {
case 0:
// do nothing;
break;
case 1:
table.setSelection(0);
break;
default:
table.setSelection(forward ? 1 : table.getItemCount() - 1);
}
tc.pack();
table.pack();
dialog.pack();
Rectangle tableBounds = table.getBounds();
tableBounds.height = Math.min(tableBounds.height, table.getItemHeight()
* MAX_ITEMS);
table.setBounds(tableBounds);
dialog.setBounds(dialog.computeTrim(tableBounds.x, tableBounds.y,
tableBounds.width, tableBounds.height));
tc.setWidth(table.getClientArea().width);
table.setFocus();
table.addFocusListener(new FocusListener() {
public void focusGained(FocusEvent e) {
// Do nothing
}
public void focusLost(FocusEvent e) {
cancel(dialog);
}
});
Rectangle dialogBounds = dialog.getBounds();
Rectangle displayBounds = display.getClientArea();
Rectangle parentBounds = dialog.getParent().getBounds();
// Place it in the center of its parent;
dialogBounds.x = parentBounds.x
+ ((parentBounds.width - dialogBounds.width) / 2);
dialogBounds.y = parentBounds.y
+ ((parentBounds.height - dialogBounds.height) / 2);
if (!displayBounds.contains(dialogBounds.x, dialogBounds.y)
|| !displayBounds.contains(dialogBounds.x + dialogBounds.width,
dialogBounds.y + dialogBounds.height)) {
// Place it in the center of the display if it is not visible
// when placed in the center of its parent;
dialogBounds.x = (displayBounds.width - dialogBounds.width) / 2;
dialogBounds.y = (displayBounds.height - dialogBounds.height) / 2;
}
dialog.setLocation(dialogBounds.x, dialogBounds.y);
table.removeHelpListener(getHelpListener());
table.addHelpListener(new HelpListener() {
public void helpRequested(HelpEvent event) {
// Do nothing
}
});
/*
* Fetch the key bindings for the forward and backward commands. They
* will not change while the dialog is open, but the context will. Bug
* 55581.
*/
final IBindingService bindingService = (IBindingService) PlatformUI
.getWorkbench().getAdapter(IBindingService.class);
forwardTriggerSequences = bindingService
.getActiveBindingsFor(commandForward);
backwardTriggerSequences = bindingService
.getActiveBindingsFor(commandBackward);
final IContextService contextService = (IContextService) page
.getWorkbenchWindow().getWorkbench().getAdapter(
IContextService.class);
try {
dialog.open();
addMouseListener(table, dialog);
contextService.registerShell(dialog,
IContextService.TYPE_NONE);
addKeyListener(table, dialog);
addTraverseListener(table);
while (!dialog.isDisposed()) {
if (!display.readAndDispatch()) {
display.sleep();
}
}
} finally {
if (!dialog.isDisposed()) {
cancel(dialog);
}
contextService.unregisterShell(dialog);
forwardTriggerSequences = null;
backwardTriggerSequences = null;
}
}
/**
* See IPageListener
*/
public void pageActivated(IWorkbenchPage page) {
super.pageActivated(page);
updateState();
}
/**
* See IPageListener
*/
public void pageClosed(IWorkbenchPage page) {
super.pageClosed(page);
updateState();
}
/**
* See IPartListener
*/
public void partClosed(IWorkbenchPart part) {
super.partClosed(part);
updateState();
}
/**
* See IPartListener
*/
public void partOpened(IWorkbenchPart part) {
super.partOpened(part);
updateState();
}
/**
* @see Action#run()
*/
public void runWithEvent(Event e) {
if (getWorkbenchWindow() == null) {
// action has been disposed
return;
}
IWorkbenchPage page = getActivePage();
openDialog((WorkbenchPage) page);
activate(page, selection);
}
/**
* Set the backward action.
*
* @param actionDefinitionId
* the action
*/
public void setBackwardActionDefinitionId(String actionDefinitionId) {
final ICommandService commandService = (ICommandService) getWorkbenchWindow()
.getWorkbench().getAdapter(ICommandService.class);
final Command command = commandService.getCommand(actionDefinitionId);
commandBackward = new ParameterizedCommand(command, null);
}
/**
* Set the forward action.
*
* @param actionDefinitionId
* the action
*/
public void setForwardActionDefinitionId(String actionDefinitionId) {
final ICommandService commandService = (ICommandService) getWorkbenchWindow()
.getWorkbench().getAdapter(ICommandService.class);
final Command command = commandService.getCommand(actionDefinitionId);
commandForward = new ParameterizedCommand(command, null);
}
/**
* Set text and tooltips in the action.
*/
protected void setText() {
// TBD: Remove text and tooltip when this becomes an invisible action.
if (forward) {
setText(WorkbenchMessages.CyclePartAction_next_text);
setToolTipText(WorkbenchMessages.CyclePartAction_next_toolTip);
// @issue missing action ids
getWorkbenchWindow().getWorkbench().getHelpSystem().setHelp(this,
IWorkbenchHelpContextIds.CYCLE_PART_FORWARD_ACTION);
setActionDefinitionId("org.eclipse.ui.window.nextView"); //$NON-NLS-1$
} else {
setText(WorkbenchMessages.CyclePartAction_prev_text);
setToolTipText(WorkbenchMessages.CyclePartAction_prev_toolTip);
// @issue missing action ids
getWorkbenchWindow().getWorkbench().getHelpSystem().setHelp(this,
IWorkbenchHelpContextIds.CYCLE_PART_BACKWARD_ACTION);
setActionDefinitionId("org.eclipse.ui.window.previousView"); //$NON-NLS-1$
}
}
/**
* Updates the enabled state.
*/
protected void updateState() {
IWorkbenchPage page = getActivePage();
if (page == null) {
setEnabled(false);
return;
}
// enable iff there is at least one other part to switch to
// (the editor area counts as one entry)
int count = page.getViewReferences().length;
if (page.getEditorReferences().length > 0) {
++count;
}
setEnabled(count >= 1);
}
}