blob: ebd1da89521c48f2ced8252719c621266ee8e34b [file] [log] [blame]
/*
* Copyright (c) 2014-2018 Eike Stepper (Loehne, Germany) and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v2.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v20.html
*
* Contributors:
* Ed Merks - initial API and implementation
*/
package org.eclipse.oomph.internal.ui;
import org.eclipse.oomph.ui.UIUtil;
import org.eclipse.oomph.util.ReflectUtil;
import org.eclipse.oomph.util.StringUtil;
import org.eclipse.emf.common.CommonPlugin;
import org.eclipse.emf.ecore.EDataType;
import org.eclipse.emf.ecore.EcorePackage;
import org.eclipse.emf.edit.domain.AdapterFactoryEditingDomain;
import org.eclipse.emf.edit.provider.IItemPropertyDescriptor;
import org.eclipse.emf.edit.provider.IItemPropertySource;
import org.eclipse.emf.edit.ui.EMFEditUIPlugin;
import org.eclipse.emf.edit.ui.provider.AdapterFactoryContentProvider;
import org.eclipse.emf.edit.ui.provider.PropertyDescriptor;
import org.eclipse.emf.edit.ui.provider.PropertySource;
import org.eclipse.emf.edit.ui.view.ExtendedPropertySheetPage;
import org.eclipse.jface.action.Action;
import org.eclipse.jface.action.IMenuManager;
import org.eclipse.jface.dialogs.Dialog;
import org.eclipse.jface.dialogs.IDialogSettings;
import org.eclipse.jface.dialogs.IInputValidator;
import org.eclipse.jface.text.IFindReplaceTarget;
import org.eclipse.jface.viewers.CellEditor;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.window.Window;
import org.eclipse.swt.SWT;
import org.eclipse.swt.SWTError;
import org.eclipse.swt.dnd.Clipboard;
import org.eclipse.swt.dnd.TextTransfer;
import org.eclipse.swt.dnd.Transfer;
import org.eclipse.swt.events.ControlAdapter;
import org.eclipse.swt.events.ControlEvent;
import org.eclipse.swt.events.KeyAdapter;
import org.eclipse.swt.events.KeyEvent;
import org.eclipse.swt.events.MouseAdapter;
import org.eclipse.swt.events.MouseEvent;
import org.eclipse.swt.events.MouseListener;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.events.TreeEvent;
import org.eclipse.swt.events.TreeListener;
import org.eclipse.swt.events.VerifyEvent;
import org.eclipse.swt.events.VerifyListener;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Menu;
import org.eclipse.swt.widgets.ScrollBar;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.Text;
import org.eclipse.swt.widgets.Tree;
import org.eclipse.swt.widgets.TreeColumn;
import org.eclipse.swt.widgets.TreeItem;
import org.eclipse.ui.ISharedImages;
import org.eclipse.ui.IWorkbenchPart;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.views.properties.IPropertyDescriptor;
import org.eclipse.ui.views.properties.IPropertySheetEntry;
import org.eclipse.ui.views.properties.IPropertySource;
import org.eclipse.ui.views.properties.IPropertySourceProvider;
import org.eclipse.ui.views.properties.PropertySheetSorter;
import java.lang.reflect.Constructor;
import java.text.MessageFormat;
/**
* @author Ed Merks
*/
public class OomphPropertySheetPage extends ExtendedPropertySheetPage
{
private static final boolean HAS_EMF_COPY_VALUE_PROPERTY_ACTION;
static
{
boolean hasEMFCopyPropertyValueAction;
try
{
ReflectUtil.getField(ExtendedPropertySheetPage.class, "copyPropertyValueAction");
hasEMFCopyPropertyValueAction = true;
}
catch (Exception ex)
{
hasEMFCopyPropertyValueAction = false;
}
HAS_EMF_COPY_VALUE_PROPERTY_ACTION = hasEMFCopyPropertyValueAction;
}
private Tree tree;
private ControlAdapter columnResizer;
private CopyValuePropertyAction copyPropertyValueAction;
private Clipboard clipboard;
private IWorkbenchPart part;
public OomphPropertySheetPage(AdapterFactoryEditingDomain editingDomain, Decoration decoration, IDialogSettings dialogSettings)
{
super(editingDomain, decoration, dialogSettings);
setSorter(new PropertySheetSorter()
{
@Override
public void sort(IPropertySheetEntry[] entries)
{
// Intentionally left empty
}
});
}
@Override
public void setRootEntry(IPropertySheetEntry entry)
{
getCopyPropertyValueAction();
super.setRootEntry(entry);
}
@Override
public void createControl(Composite parent)
{
super.createControl(parent);
addColumnResizer();
if (!HAS_EMF_COPY_VALUE_PROPERTY_ACTION)
{
Menu menu = getControl().getMenu();
IMenuManager menuManager = (IMenuManager)menu.getData("org.eclipse.jface.action.MenuManager.managerKey");
menuManager.insertAfter("copy", getCopyPropertyValueAction());
}
}
private Action getCopyPropertyValueAction()
{
if (!HAS_EMF_COPY_VALUE_PROPERTY_ACTION && copyPropertyValueAction == null)
{
Shell shell = getControl().getShell();
clipboard = new Clipboard(shell.getDisplay());
copyPropertyValueAction = new CopyValuePropertyAction(clipboard);
}
return copyPropertyValueAction;
}
private void addColumnResizer()
{
tree = (Tree)getControl();
tree.addTreeListener(new TreeListener()
{
Runnable runnable = new Runnable()
{
public void run()
{
resizeColumns();
}
};
public void treeExpanded(TreeEvent e)
{
UIUtil.asyncExec(tree, runnable);
}
public void treeCollapsed(TreeEvent e)
{
UIUtil.asyncExec(tree, runnable);
}
});
final TreeColumn propertyColumn = tree.getColumn(0);
propertyColumn.setResizable(false);
final TreeColumn valueColumn = tree.getColumn(1);
valueColumn.setResizable(false);
columnResizer = new ControlAdapter()
{
private int clientWidth = -1;
private int propertyWidth = -1;
private int valueWidth = -1;
private boolean resizing;
@Override
public void controlResized(ControlEvent e)
{
if (resizing)
{
return;
}
Rectangle clientArea = tree.getClientArea();
int clientWidth = clientArea.width - clientArea.x;
ScrollBar bar = tree.getVerticalBar();
if (bar != null && bar.isVisible())
{
clientWidth -= bar.getSize().x;
}
int propertyWidth = propertyColumn.getWidth();
int valueWidth = valueColumn.getWidth();
boolean inputChanged = e == null;
if (inputChanged || clientWidth != this.clientWidth || propertyWidth != this.propertyWidth || valueWidth != this.valueWidth)
{
try
{
resizing = true;
tree.setRedraw(false);
TreeItem[] items = tree.getItems();
if (items.length == 0)
{
propertyWidth = clientWidth / 2;
propertyColumn.setWidth(propertyWidth);
valueColumn.setWidth(clientWidth - propertyWidth);
}
else
{
propertyColumn.pack();
propertyWidth = propertyColumn.getWidth() + 20;
propertyColumn.setWidth(propertyWidth);
valueColumn.pack();
valueWidth = valueColumn.getWidth();
if (propertyWidth + valueWidth < clientWidth)
{
valueWidth = clientWidth - propertyWidth;
valueColumn.setWidth(valueWidth);
}
}
}
finally
{
this.clientWidth = clientWidth;
this.propertyWidth = propertyWidth;
this.valueWidth = valueWidth;
tree.setRedraw(true);
resizing = false;
}
}
}
};
tree.addControlListener(columnResizer);
propertyColumn.addControlListener(columnResizer);
valueColumn.addControlListener(columnResizer);
resizeColumns();
}
private void resizeColumns()
{
columnResizer.controlResized(null);
}
@Override
public void selectionChanged(IWorkbenchPart part, ISelection selection)
{
this.part = part;
super.selectionChanged(part, selection);
resizeColumns();
}
@Override
public void handleEntrySelection(ISelection selection)
{
super.handleEntrySelection(selection);
if (copyPropertyValueAction != null)
{
copyPropertyValueAction.selectionChanged((IStructuredSelection)selection);
}
}
@SuppressWarnings("all")
@Override
public Object getAdapter(Class adapter)
{
if (adapter == IFindReplaceTarget.class)
{
return part.getAdapter(adapter);
}
return super.getAdapter(adapter);
}
@Override
public void setPropertySourceProvider(IPropertySourceProvider newProvider)
{
if (newProvider instanceof AdapterFactoryContentProvider)
{
final AdapterFactoryContentProvider adapterFactoryContentProvider = (AdapterFactoryContentProvider)newProvider;
super.setPropertySourceProvider(new IPropertySourceProvider()
{
public IPropertySource getPropertySource(Object object)
{
IPropertySource propertySource = adapterFactoryContentProvider.getPropertySource(object);
if (propertySource instanceof PropertySource && propertySource.getClass() == PropertySource.class)
{
return new PropertySource(object, ReflectUtil.<IItemPropertySource> getValue("itemPropertySource", propertySource))
{
@Override
protected IPropertyDescriptor createPropertyDescriptor(IItemPropertyDescriptor itemPropertyDescriptor)
{
return new OomphPropertyDescriptor(object, itemPropertyDescriptor);
}
};
}
return propertySource;
}
});
}
else
{
super.setPropertySourceProvider(newProvider);
}
}
@Override
public void dispose()
{
super.dispose();
if (clipboard != null)
{
clipboard.dispose();
}
}
/**
* @author Eike Stepper
*/
public static class OomphPropertyDescriptor extends PropertyDescriptor
{
private static final EDataTypeValueHandler NO_OP_VALUE_HANDLER = new EDataTypeValueHandler(EcorePackage.Literals.ESTRING);
public OomphPropertyDescriptor(Object object, IItemPropertyDescriptor itemPropertyDescriptor)
{
super(object, itemPropertyDescriptor);
}
@Override
protected CellEditor createEDataTypeCellEditor(EDataType eDataType, Composite composite)
{
if (itemPropertyDescriptor.isMultiLine(object))
{
return new EDataTypeCellEditor(eDataType, composite)
{
private Text text;
private Button button;
private boolean doEscape;
private String value;
private MouseListener mouseListener = new MouseAdapter()
{
@Override
public void mouseUp(MouseEvent e)
{
showDialog();
}
@Override
public void mouseDoubleClick(MouseEvent e)
{
showDialog();
}
};
private VerifyListener verifyListener = new VerifyListener()
{
public void verifyText(VerifyEvent e)
{
e.doit = false;
showDialog();
}
};
private void showDialog()
{
button.setFocus();
UIUtil.asyncExec(new Runnable()
{
public void run()
{
button.notifyListeners(SWT.Selection, null);
}
});
}
@Override
public Object doGetValue()
{
String str = value != null ? value : (String)super.doGetValue();
if (doEscape)
{
str = StringUtil.unescape(str);
}
return str;
}
@Override
public void doSetValue(Object value)
{
text.removeMouseListener(mouseListener);
text.removeVerifyListener(verifyListener);
String str = valueHandler.toString(value);
if (str != null)
{
for (char c : str.toCharArray())
{
if (Character.isISOControl(c))
{
doEscape = true;
str = StringUtil.escape(str);
break;
}
}
}
EDataTypeValueHandler oldValueHandler = valueHandler;
try
{
boolean isVeryLong = str.length() > 2000;
if (isVeryLong)
{
this.value = str;
str = str.substring(0, 2000);
}
else
{
this.value = null;
}
valueHandler = NO_OP_VALUE_HANDLER;
super.doSetValue(str);
if (isVeryLong)
{
text.addMouseListener(mouseListener);
text.addVerifyListener(verifyListener);
}
}
finally
{
valueHandler = oldValueHandler;
}
}
@Override
protected Control createControl(Composite parent)
{
GridLayout layout = new GridLayout(2, false);
layout.marginWidth = 0;
layout.marginHeight = 0;
layout.horizontalSpacing = 0;
final Composite composite = new Composite(parent, SWT.NONE);
composite.setLayout(layout);
composite.setFont(parent.getFont());
composite.setBackground(parent.getBackground());
text = (Text)super.createControl(composite);
text.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
button = new Button(composite, SWT.PUSH);
button.setLayoutData(new GridData(SWT.FILL, SWT.FILL, false, true));
button.setText("...");
button.addSelectionListener(new SelectionAdapter()
{
@Override
public void widgetSelected(SelectionEvent e)
{
final String className = "org.eclipse.emf.edit.ui.provider.PropertyDescriptor$MultiLineInputDialog";
Dialog dialog;
String stringValue = valueHandler.toString(getValue());
boolean containsNull = stringValue.indexOf('\u0000') != -1;
if (containsNull)
{
stringValue = stringValue.replace('\u0000', '\n');
}
try
{
Class<?> c = CommonPlugin.loadClass("org.eclipse.emf.edit.ui", className);
if (c == null)
{
throw new Exception("Could not find class " + className);
}
Constructor<?> constructor = c.getConstructor(Shell.class, String.class, String.class, String.class, IInputValidator.class);
if (constructor == null)
{
throw new Exception("Could not find constructor of " + className);
}
constructor.setAccessible(true);
dialog = (Dialog)constructor.newInstance(composite.getShell(),
EMFEditUIPlugin.INSTANCE.getString("_UI_FeatureEditorDialog_title",
new Object[] { getDisplayName(), getEditLabelProvider().getText(object) }),
EMFEditUIPlugin.INSTANCE.getString("_UI_MultiLineInputDialog_message"), stringValue, valueHandler);
if (dialog == null)
{
throw new Exception("Could not open dialog " + className);
}
}
catch (Throwable ex)
{
UIPlugin.INSTANCE.log(ex);
return;
}
int shellStyle = (Integer)ReflectUtil.invokeMethod("getShellStyle", dialog);
ReflectUtil.invokeMethod(ReflectUtil.getMethod(dialog, "setShellStyle", int.class), dialog, shellStyle | SWT.MAX);
if (dialog.open() == Window.OK)
{
String value = (String)ReflectUtil.invokeMethod("getValue", dialog);
value = value.replaceAll("\r\n", "\n");
if (containsNull)
{
value = value.replace('\n', '\u0000');
}
Object newValue = valueHandler.toValue(value);
if (newValue != null)
{
boolean newValidState = isCorrect(newValue);
if (newValidState)
{
markDirty();
doSetValue(newValue);
fireApplyEditorValue();
}
else
{
// try to insert the current value into the error message.
setErrorMessage(MessageFormat.format(getErrorMessage(), new Object[] { newValue.toString() }));
}
}
}
else
{
fireCancelEditor();
}
}
});
button.addKeyListener(new KeyAdapter()
{
@Override
public void keyReleased(KeyEvent e)
{
if (e.character == '\u001b')
{
fireCancelEditor();
}
}
});
return composite;
}
@Override
protected void focusLost()
{
if (isActivated())
{
UIUtil.asyncExec(new Runnable()
{
public void run()
{
try
{
if (button.isFocusControl())
{
return;
}
}
catch (Exception ex)
{
//$FALL-THROUGH$
}
try
{
fireApplyEditorValue();
deactivate();
}
catch (Exception ex)
{
//$FALL-THROUGH$
}
}
});
}
}
};
}
return super.createEDataTypeCellEditor(eDataType, composite);
}
}
/**
* @author Ed Merks
*/
private static class CopyValuePropertyAction extends Action
{
private Clipboard clipboard;
private IStructuredSelection selection;
public CopyValuePropertyAction(Clipboard clipboard)
{
super("Copy &Value");
this.clipboard = clipboard;
setImageDescriptor(PlatformUI.getWorkbench().getSharedImages().getImageDescriptor(ISharedImages.IMG_TOOL_COPY));
}
@Override
public void run()
{
if (selection != null && !selection.isEmpty())
{
IPropertySheetEntry entry = (IPropertySheetEntry)selection.getFirstElement();
String value = entry.getValueAsString();
if (value != null)
{
setClipboard(value);
}
}
}
public void selectionChanged(IStructuredSelection selection)
{
this.selection = selection;
setEnabled(!selection.isEmpty());
}
private void setClipboard(String text)
{
try
{
Object[] data = new Object[] { text };
Transfer[] transferTypes = new Transfer[] { TextTransfer.getInstance() };
clipboard.setContents(data, transferTypes);
}
catch (SWTError ex)
{
UIPlugin.INSTANCE.log(ex);
}
}
}
}