package org.marketcetera.photon.internal.module.ui;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Pattern;
import org.eclipse.jface.action.Action;
import org.eclipse.jface.action.IMenuListener;
import org.eclipse.jface.action.IMenuManager;
import org.eclipse.jface.action.MenuManager;
import org.eclipse.jface.dialogs.IDialogConstants;
import org.eclipse.jface.layout.GridDataFactory;
import org.eclipse.jface.layout.GridLayoutFactory;
import org.eclipse.jface.preference.PreferencePage;
import org.eclipse.jface.viewers.CellEditor;
import org.eclipse.jface.viewers.StructuredSelection;
import org.eclipse.jface.viewers.TextCellEditor;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
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.Label;
import org.eclipse.swt.widgets.Menu;
import org.eclipse.swt.widgets.Tree;
import org.eclipse.swt.widgets.TreeItem;
import org.eclipse.swt.widgets.Widget;
import org.eclipse.ui.IWorkbench;
import org.eclipse.ui.IWorkbenchPreferencePage;
import org.eclipse.ui.views.properties.IPropertyDescriptor;
import org.eclipse.ui.views.properties.IPropertySource;
import org.eclipse.ui.views.properties.IPropertySourceProvider;
import org.eclipse.ui.views.properties.PropertyDescriptor;
import org.eclipse.ui.views.properties.PropertySheetEntry;
import org.eclipse.ui.views.properties.PropertySheetPage;
import org.eclipse.ui.views.properties.TextPropertyDescriptor;
import org.marketcetera.photon.module.IModuleAttributeDefaults;
import org.marketcetera.photon.module.IModuleAttributeSupport;
import org.marketcetera.photon.module.ModuleSupport;
import org.marketcetera.photon.module.ui.NewPropertyInputDialog;
import org.marketcetera.util.misc.ClassVersion;
/* $License$ */
/**
* The Module Properties preference page. All properties are stored in a single
* Eclipse runtime preference.
*
* @author <a href="mailto:will@marketcetera.com">Will Horn</a>
* @version $Id: ModulePropertiesPreferencePage.java 9999 2008-11-04 22:49:55Z
* will $
* @since 1.0.0
*/
@ClassVersion("$Id: ModulePropertiesPreferencePage.java 16154 2012-07-14 16:34:05Z colin $")
public final class ModulePropertiesPreferencePage extends PreferencePage
implements IWorkbenchPreferencePage {
/**
* Properties separator character.
*/
private static final String SEPARATOR = "."; //$NON-NLS-1$
/**
* Pattern to split properties.
*/
private static final Pattern SEPARATOR_PATTERN = Pattern.compile("\\."); //$NON-NLS-1$
/**
* Pattern to identify passwords.
*/
private static final Pattern PASSWORD_PATTERN = Pattern.compile(Messages.MODULE_PROPERTIES_PREFERENCE_PAGE_PASSWORD_MATCH.getText(), Pattern.CASE_INSENSITIVE);
/**
* Masks passwords.
*/
private static final String PASSWORD_MASK = "**********"; //$NON-NLS-1$
/**
* Holds the properties being edited
*/
private final PropertiesTree mProperties;
/**
* Root of the UI
*/
private PropertySheetPage mPage;
private final PreferencesAdapter mPreferencesAdapter;
/**
* Default Constructor.
*
* Initialized by extension point.
*/
public ModulePropertiesPreferencePage() {
mPreferencesAdapter = new PreferencesAdapter(ModuleSupport.getModuleAttributeSupport());
mProperties = mPreferencesAdapter.toTree();
}
@Override
public void init(IWorkbench workbench) {
}
@Override
protected Control createContents(Composite parent) {
Composite composite = new Composite(parent, SWT.NO_FOCUS);
GridLayoutFactory.fillDefaults().applyTo(composite);
Label warningLabel = new Label(composite, SWT.WRAP);
warningLabel
.setText(Messages.MODULE_PROPERTIES_PREFERENCE_PAGE_RESTART_WARNING
.getText());
GridDataFactory.defaultsFor(warningLabel).applyTo(warningLabel);
// Nest the property sheet page used by the Properties view.
mPage = new PropertySheetPage();
mPage.setPropertySourceProvider(new IPropertySourceProvider() {
@Override
public IPropertySource getPropertySource(Object object) {
if (object instanceof IPropertySource)
return (IPropertySource) object;
return null;
}
});
mPage.createControl(composite);
GridDataFactory.fillDefaults().grab(true, true).applyTo(
mPage.getControl());
// Simulate selection of a root property ""
mPage.selectionChanged(null, new StructuredSelection(
new ModulePropertyNode(""))); //$NON-NLS-1$
// By default properties are lazily loaded when the user expands nodes,
// but here we want the user can see all the properties right away.
expandAll();
// Right click actions
initPopupMenu();
return composite;
}
private void initPopupMenu() {
MenuManager menuMgr = new MenuManager("#PopupMenu"); //$NON-NLS-1$
menuMgr.setRemoveAllWhenShown(true);
menuMgr.addMenuListener(new IMenuListener() {
@Override
public void menuAboutToShow(IMenuManager manager) {
final TreeItem[] selection = ((Tree) mPage.getControl())
.getSelection();
// Add
if (selection.length <= 1) {
manager
.add(new Action(
Messages.MODULE_PROPERTIES_PREFERENCE_PAGE_ADD_ACTION__LABEL
.getText()) {
@Override
public void run() {
String key = ""; //$NON-NLS-1$
if (selection.length == 1)
key = ((ModulePropertyNode) ((PropertySheetEntry) selection[0]
.getData()).getValues()[0]).mKey
+ SEPARATOR;
// Show Instance Defaults if the selected
// property
// is level 2, i.e. a module provider
final boolean allowInstanceDefault = (SEPARATOR_PATTERN
.split(key).length == 2);
NewPropertyInputDialog dialog = new NewPropertyInputDialog(
getShell(), allowInstanceDefault);
if (dialog.open() == IDialogConstants.OK_ID) {
if (dialog.isInstanceDefault())
key += IModuleAttributeDefaults.INSTANCE_DEFAULTS_IDENTIFIER
+ SEPARATOR;
key += dialog.getPropertyKey();
if (!mProperties.containsKey(key))
mProperties.put(key, dialog
.getPropertyValue());
mPage.refresh();
if (selection.length == 1) {
expand(selection[0]);
} else {
expandAll();
}
}
}
});
}
// Delete
if (selection.length >= 1) {
manager
.add(new Action(
Messages.MODULE_PROPERTIES_PREFERENCE_PAGE_DELETE_ACTION__LABEL
.getText()) {
@Override
public void run() {
for (int i = 0; i < selection.length; i++) {
final String root = ((ModulePropertyNode) ((PropertySheetEntry) selection[i]
.getData()).getValues()[0]).mKey;
mProperties.remove(root);
}
mPage.refresh();
}
});
}
}
});
Menu menu = menuMgr.createContextMenu(mPage.getControl());
mPage.getControl().setMenu(menu);
}
@Override
protected void contributeButtons(Composite parent) {
// A button to add properties
Button button = new Button(parent, SWT.PUSH);
button
.setText(Messages.MODULE_PROPERTIES_PREFERENCE_PAGE_ADD_BUTTON__LABEL
.getText());
GridDataFactory.defaultsFor(button).applyTo(button);
button.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent e) {
NewPropertyInputDialog dialog = new NewPropertyInputDialog(
getShell(), false);
if (dialog.open() == IDialogConstants.OK_ID) {
final String key = dialog.getPropertyKey();
if (!mProperties.containsKey(key))
mProperties.put(key, dialog.getPropertyValue());
mPage.refresh();
expandAll();
}
}
});
((GridLayout) parent.getLayout()).numColumns++;
}
/**
* Helper method to expand entire property tree
*/
private void expandAll() {
Tree tree = (Tree) mPage.getControl();
for (TreeItem item : tree.getItems()) {
expand(item);
}
}
/**
* Helper method to expand an item in the property tree.
*
* @param item
* tree item to expand.
*/
private void expand(TreeItem item) {
// try to expand the tree using reflection
try {
Field field = mPage.getClass().getDeclaredField("viewer"); //$NON-NLS-1$
field.setAccessible(true);
Object viewer = field.get(mPage);
Method method = viewer.getClass().getDeclaredMethod(
"createChildren", Widget.class); //$NON-NLS-1$
method.setAccessible(true);
method.invoke(viewer, item);
} catch (Exception e) {
// something went wrong - user can still manually expand
return;
}
item.setExpanded(true);
// recurse
for (TreeItem child : item.getItems()) {
expand(child);
}
}
@Override
public boolean performOk() {
mPreferencesAdapter.fromTree(mProperties);
ModuleSupport.getModuleAttributeSupport().flush();
return true;
}
/**
* {@link IPropertySource} for adapting a {@link PropertiesTree} for
* the standard property sheet.
*
* This class also serves as the ID object for property descriptors.
*
* @author <a href="mailto:will@marketcetera.com">Will Horn</a>
* @version $Id: ModulePropertiesPreferencePage.java 9999 2008-11-04
* 22:49:55Z will $
* @since 1.0.0
*/
@ClassVersion("$Id: ModulePropertiesPreferencePage.java 16154 2012-07-14 16:34:05Z colin $")
private final class ModulePropertyNode implements IPropertySource {
String mKey;
/**
* Constructor.
*
* @param key
* key this node is rooted at
*/
ModulePropertyNode(String key) {
super();
mKey = key;
}
@Override
public int hashCode() {
return mKey.hashCode();
}
@Override
public boolean equals(Object obj) {
return mKey.equals(((ModulePropertyNode) obj).mKey);
}
@Override
public Object getEditableValue() {
final String value = mProperties.get(mKey);
if (value != null) {
// mask text if the key is a password
final String[] split = SEPARATOR_PATTERN.split(mKey);
final String display = split[split.length - 1];
return (!value.isEmpty() && PASSWORD_PATTERN.matcher(display).matches()) ? PASSWORD_MASK : value;
}
return null;
}
@Override
public IPropertyDescriptor[] getPropertyDescriptors() {
List<IPropertyDescriptor> descriptors = new ArrayList<IPropertyDescriptor>();
for (String prefix : mProperties.getChildKeys(mKey)) {
final String[] split = SEPARATOR_PATTERN.split(prefix);
final String display = split[split.length - 1];
if (split.length <= 2) {
descriptors.add(new PropertyDescriptor(
new ModulePropertyNode(prefix), display));
} else {
if (PASSWORD_PATTERN.matcher(display).matches()) {
// mask text if the key is a password
descriptors.add(new PropertyDescriptor(
new ModulePropertyNode(prefix), display) {
@Override
public CellEditor createPropertyEditor(
Composite parent) {
return new TextCellEditor(parent, SWT.PASSWORD);
}
});
} else {
descriptors.add(new TextPropertyDescriptor(
new ModulePropertyNode(prefix), display
.equals(IModuleAttributeSupport.INSTANCE_DEFAULTS_IDENTIFIER) ? "*Instance Defaults*" //$NON-NLS-1$
: display));
}
}
}
return (IPropertyDescriptor[]) descriptors
.toArray(new IPropertyDescriptor[descriptors.size()]);
}
@Override
public Object getPropertyValue(Object id) {
// The id of a subtree is the subtree itself. By returning it here,
// the properties viewer will recurse into it and use
// getEditableValue
if (id instanceof ModulePropertyNode)
return id;
else
return mProperties.get(mKey);
}
@Override
public boolean isPropertySet(Object id) {
// no defaults
return false;
}
@Override
public void resetPropertyValue(Object id) {
// no defaults
}
@Override
public void setPropertyValue(Object id, Object value) {
if (value == null)
return;
String key = ((ModulePropertyNode) id).mKey;
mProperties.put(key, (String) value);
}
}
}