blob: 3a7ff1734a054bccd6ed3517d0796f58ea597162 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2011, 2021 Wind River Systems, Inc. and others. All rights reserved.
* This program and the accompanying materials are made available under the terms
* of the Eclipse Public License 2.0 which accompanies this distribution, and is
* available at https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Wind River Systems - initial API and implementation
*******************************************************************************/
package org.eclipse.tm.terminal.view.ui.tabs;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import org.eclipse.core.runtime.Assert;
import org.eclipse.core.runtime.PlatformObject;
import org.eclipse.jface.action.IContributionItem;
import org.eclipse.jface.action.IMenuListener2;
import org.eclipse.jface.action.IMenuManager;
import org.eclipse.jface.action.MenuManager;
import org.eclipse.jface.action.Separator;
import org.eclipse.swt.custom.CTabFolder;
import org.eclipse.swt.custom.CTabItem;
import org.eclipse.swt.dnd.DND;
import org.eclipse.swt.dnd.TextTransfer;
import org.eclipse.swt.dnd.Transfer;
import org.eclipse.swt.widgets.Menu;
import org.eclipse.tm.internal.terminal.control.ITerminalViewControl;
import org.eclipse.tm.internal.terminal.control.actions.AbstractTerminalAction;
import org.eclipse.tm.internal.terminal.control.actions.TerminalActionClearAll;
import org.eclipse.tm.internal.terminal.control.actions.TerminalActionCopy;
import org.eclipse.tm.internal.terminal.control.actions.TerminalActionPaste;
import org.eclipse.tm.internal.terminal.control.actions.TerminalActionSelectAll;
import org.eclipse.tm.internal.terminal.provisional.api.TerminalState;
import org.eclipse.tm.terminal.view.core.interfaces.constants.ITerminalsConnectorConstants;
import org.eclipse.tm.terminal.view.ui.actions.RenameTerminalAction;
import org.eclipse.tm.terminal.view.ui.actions.InvertColorsAction;
import org.eclipse.tm.terminal.view.ui.actions.SelectEncodingAction;
import org.eclipse.tm.terminal.view.ui.interfaces.ITerminalsView;
import org.eclipse.ui.IWorkbenchActionConstants;
/**
* Terminal tab folder menu handler.
*/
public class TabFolderMenuHandler extends PlatformObject {
// Reference to the parent terminals console view
private final ITerminalsView parentView;
// Reference to the tab folder context menu manager
private MenuManager contextMenuManager;
// Reference to the tab folder context menu
private Menu contextMenu;
// The list of actions available within the context menu
private final List<AbstractTerminalAction> contextMenuActions = new ArrayList<>();
// The list of invalid context menu contributions "startsWith" expressions
/* default */ static final String[] INVALID_CONTRIBUTIONS_STARTS_WITH = { "org.eclipse.cdt", "org.eclipse.ui.edit" //$NON-NLS-1$ //$NON-NLS-2$
};
/**
* Default menu listener implementation.
*/
protected class MenuListener implements IMenuListener2 {
@Override
public void menuAboutToHide(IMenuManager manager) {
// CQ:WIND00192293 and CQ:WIND194204 - don't update actions on menuAboutToHide
// See also http://bugs.eclipse.org/296212
// updateMenuItems(false);
}
@Override
public void menuAboutToShow(IMenuManager manager) {
removeInvalidContributions(manager);
updateMenuItems(true);
}
/**
* Bug 392249: Remove contributions that appear in the context in Eclipse 4.x which are
* not visible in Eclipse 3.8.x. Re-evaluate from time to time!
*
* @param manager The menu manager or <code>null</code>
*/
private void removeInvalidContributions(IMenuManager manager) {
if (manager == null)
return;
IContributionItem[] items = manager.getItems();
for (IContributionItem item : items) {
String id = item.getId();
if (id != null) {
for (String prefix : INVALID_CONTRIBUTIONS_STARTS_WITH) {
if (id.startsWith(prefix)) {
manager.remove(item);
break;
}
}
}
}
}
}
/**
* Constructor.
*
* @param parentView The parent terminals console view. Must not be <code>null</code>.
*/
public TabFolderMenuHandler(ITerminalsView parentView) {
super();
Assert.isNotNull(parentView);
this.parentView = parentView;
}
/**
* Returns the parent terminals console view.
*
* @return The parent terminals console view instance.
*/
protected final ITerminalsView getParentView() {
return parentView;
}
/**
* Returns the tab folder associated with the parent view.
*
* @return The tab folder or <code>null</code>.
*/
protected final CTabFolder getTabFolder() {
return getParentView().getAdapter(CTabFolder.class);
}
/**
* Dispose the tab folder menu handler instance.
*/
public void dispose() {
// Dispose the context menu
if (contextMenu != null) {
contextMenu.dispose();
contextMenu = null;
}
// Dispose the context menu manager
if (contextMenuManager != null) {
contextMenuManager.dispose();
contextMenuManager = null;
}
// Clear all actions
contextMenuActions.clear();
}
/**
* Setup the context menu for the tab folder. The method will return
* immediately if the menu handler had been initialized before.
*
* @param tabFolder The tab folder control. Must not be <code>null</code>.
*/
public void initialize() {
// Return immediately if the menu manager and menu got initialized already
if (contextMenuManager != null && contextMenu != null) {
return;
}
// Get the tab folder
CTabFolder tabFolder = getTabFolder();
if (tabFolder == null) {
return;
}
// Create the menu manager if not done before
contextMenuManager = new MenuManager("#PopupMenu"); //$NON-NLS-1$
// Bug 392249: Register our menu listener after registering the context menu
// for contributions. That way we can use our menu listener to get
// rid of unwanted/misguided contributions. At least until this is
// fixed in the Eclipse 4.x platform.
// Create the context menu
contextMenu = contextMenuManager.createContextMenu(tabFolder);
// Temporarily set the menu on the tab folder to avoid the case
// where the menu has a different parent shell than the control.
// This can be the case if the tab folder is re-parented to the
// "PartRenderingEngine's limbo".
tabFolder.setMenu(contextMenu);
// Create the context menu action instances
doCreateContextMenuActions();
// Fill the context menu
doFillContextMenu(contextMenuManager);
// Register to the view site to open the menu for contributions
getParentView().getSite().registerContextMenu(contextMenuManager,
getParentView().getSite().getSelectionProvider());
// Create and associated the menu listener
contextMenuManager.addMenuListener(new MenuListener());
}
/**
* Adds the given action to the context menu actions list.
*
* @param action The action instance. Must not be <code>null</code>.
*/
protected final void add(AbstractTerminalAction action) {
Assert.isNotNull(action);
contextMenuActions.add(action);
}
/**
* Create the context menu actions.
*/
protected void doCreateContextMenuActions() {
// Create and add the copy action
add(new TerminalActionCopy() {
@Override
protected ITerminalViewControl getTarget() {
return getActiveTerminalViewControl();
}
});
// Create and add the paste action
add(new TerminalActionPaste() {
@SuppressWarnings({ "unchecked" })
@Override
public void run() {
// Determine if pasting to the active tab require backslash translation
boolean needsTranslation = false;
TabFolderManager manager = getParentView().getAdapter(TabFolderManager.class);
if (manager != null) {
// If we have the active tab item, we can get the active terminal control
CTabItem activeTabItem = manager.getActiveTabItem();
if (activeTabItem != null) {
Map<String, Object> properties = (Map<String, Object>) activeTabItem.getData("properties"); //$NON-NLS-1$
if (properties != null && properties
.containsKey(ITerminalsConnectorConstants.PROP_TRANSLATE_BACKSLASHES_ON_PASTE)) {
Object value = properties
.get(ITerminalsConnectorConstants.PROP_TRANSLATE_BACKSLASHES_ON_PASTE);
needsTranslation = value instanceof Boolean ? ((Boolean) value).booleanValue() : false;
}
}
}
if (needsTranslation) {
ITerminalViewControl target = getTarget();
if (target != null && target.getClipboard() != null && !target.getClipboard().isDisposed()) {
String text = (String) target.getClipboard().getContents(TextTransfer.getInstance());
if (text != null) {
text = text.replace('\\', '/');
Object[] data = new Object[] { text };
Transfer[] types = new Transfer[] { TextTransfer.getInstance() };
target.getClipboard().setContents(data, types, DND.CLIPBOARD);
}
}
}
super.run();
}
@Override
protected ITerminalViewControl getTarget() {
return getActiveTerminalViewControl();
}
});
// Create and add the clear all action
add(new TerminalActionClearAll() {
@Override
protected ITerminalViewControl getTarget() {
return getActiveTerminalViewControl();
}
@Override
public void updateAction(boolean aboutToShow) {
super.updateAction(aboutToShow);
if (getTarget() != null && getTarget().getState() != TerminalState.CONNECTED) {
setEnabled(false);
}
}
});
// Create and add the select all action
add(new TerminalActionSelectAll() {
@Override
protected ITerminalViewControl getTarget() {
return getActiveTerminalViewControl();
}
});
// Create and add the select encoding action
add(new SelectEncodingAction(getParentView().getAdapter(TabFolderManager.class)) {
@Override
protected ITerminalViewControl getTarget() {
return getActiveTerminalViewControl();
}
});
// Create and add the invert colors action
add(new InvertColorsAction(getParentView().getAdapter(TabFolderManager.class)) {
@Override
protected ITerminalViewControl getTarget() {
return getActiveTerminalViewControl();
}
});
// change the name of the terminal
add(new RenameTerminalAction(getParentView().getAdapter(TabFolderManager.class)) {
@Override
protected ITerminalViewControl getTarget() {
return getActiveTerminalViewControl();
}
});
}
/**
* Returns the currently active terminal control.
*
* @return The currently active terminal control or <code>null</code>.
*/
protected ITerminalViewControl getActiveTerminalViewControl() {
ITerminalViewControl terminal = null;
// Get the active tab item from the tab folder manager
TabFolderManager manager = getParentView().getAdapter(TabFolderManager.class);
if (manager != null) {
// If we have the active tab item, we can get the active terminal control
CTabItem activeTabItem = manager.getActiveTabItem();
if (activeTabItem != null) {
terminal = (ITerminalViewControl) activeTabItem.getData();
}
}
return terminal;
}
/**
* Fill in the context menu content within the given manager.
*
* @param manager The menu manager. Must not be <code>null</code>.
*/
protected void doFillContextMenu(MenuManager manager) {
Assert.isNotNull(manager);
// Loop all actions and add them to the menu manager
for (AbstractTerminalAction action : contextMenuActions) {
manager.add(action);
// Add a separator after the paste action
if (action instanceof TerminalActionPaste) {
manager.add(new Separator());
}
// Add a separator after the select all action
if (action instanceof TerminalActionSelectAll) {
manager.add(new Separator());
}
}
// Menu contributions will end up here
manager.add(new Separator());
manager.add(new Separator(IWorkbenchActionConstants.MB_ADDITIONS));
}
/**
* Update the context menu items on showing or hiding the context menu.
*
* @param aboutToShow <code>True</code> if the menu is about to show, <code>false</code> otherwise.
*/
protected void updateMenuItems(boolean aboutToShow) {
// Loop all actions and update the status
for (AbstractTerminalAction action : contextMenuActions) {
action.updateAction(aboutToShow);
}
}
@Override
public <T> T getAdapter(Class<T> adapter) {
if (MenuManager.class.isAssignableFrom(adapter)) {
return adapter.cast(contextMenuManager);
} else if (Menu.class.isAssignableFrom(adapter)) {
if (contextMenu == null || contextMenu.isDisposed()) {
contextMenu = contextMenuManager.createContextMenu(getTabFolder());
}
// Clear the menu from the tab folder now - see initialize()
getTabFolder().setMenu(null);
return adapter.cast(contextMenu);
}
// Try the parent view
T adapted = getParentView().getAdapter(adapter);
if (adapted != null) {
return adapted;
}
return super.getAdapter(adapter);
}
}