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); } } }