/*
* � Copyright IBM Corp. 2011
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
* implied. See the License for the specific language governing
* permissions and limitations under the License.
*/
package com.ibm.xsp.extlib.designer.tooling.propeditor;
import java.util.ArrayList;
import java.util.HashMap;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.jface.dialogs.IDialogConstants;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.events.SelectionListener;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Table;
import org.eclipse.swt.widgets.TableItem;
import org.eclipse.ui.IEditorInput;
import org.eclipse.ui.IFileEditorInput;
import org.eclipse.ui.IWorkbenchPart;
import org.eclipse.ui.part.EditorPart;
import com.ibm.commons.iloader.node.lookups.api.ILookup;
import com.ibm.commons.iloader.node.lookups.api.StringLookup;
import com.ibm.commons.swt.SWTLayoutUtils;
import com.ibm.commons.swt.controls.custom.CustomCombo;
import com.ibm.commons.swt.controls.custom.CustomTable;
import com.ibm.commons.swt.data.dialog.LWPDNoTitleAreaDialog;
import com.ibm.commons.swt.data.editors.api.AbstractComboEditor;
import com.ibm.commons.swt.data.editors.api.AbstractTextEditor;
import com.ibm.commons.swt.data.editors.api.CompositeEditor;
import com.ibm.commons.swt.data.editors.api.PropertyEditor;
import com.ibm.commons.util.StringUtil;
import com.ibm.designer.domino.ide.resources.extensions.DesignerDesignElement;
import com.ibm.designer.domino.ide.resources.extensions.DesignerProject;
import com.ibm.designer.domino.ui.commons.extensions.DesignerResource;
/**
* This is the base class that design element property pickers should extend.
* Subclasses must include an implementation of getDesignElementIDs() which returns a String[] containing
* the design element IDs (as specified in DesignResource.java) to be displayed in the picker.
* A subclass can provide one or more IDs in the String[].
*
* This class will compile the collection of Design Elements from the Design Project, but subclasses must
* implement createDesignElementLookup(DesignerDesignElement[] designElements) to return the labels to display
* in the picker for the given design elements and the values to set in source for those design elements.
*
* If a single Design Element id is provided by getDesignElementIDs(), a simple comboBox is created for the picker,
* using the StringLookup from createDesignElementLookup(DesignerDesignElement[] designElements).
* If multiple Design Element IDs are provided, then this class provides a dialog that will be used to select
* the design elements. The dialog has a comboBox to choose the Design Element Type and a table that provides
* the list of design elements for the selected type.
*
* By default the design element type comboBox in the dialog will display the Design Element Type id. If a subclass
* would like to provide better labels, the can do so by overriding getLabelForDesignElementID(String designElementId).
* A subclass can also set the title of the Dialog by overriding getDialogTitleText().
*
* If the dialog is used, no comboBox cell editor is provided. It will only have the button to pop up the dialog. If a
* subclass would rather just have a comboBox cell editor that contains multiple design element types, they can provide
* multiple design element IDs in getDesignElementIDs() and override useDialog() to return false.
*
* If a subclass specifies a single design element id,they can use the dialog by overriding useDialog() to return true.
* In that scenario, the dialog will not contain the comboBox to select the type. Instead it will just contain the
* table of design element for the specified design element id.
*
*/
public abstract class AbstractDesignElementPicker extends PropertyEditor {
//This is the title of the dialog we pop up to choose the options
private static String DIALOG_TITLE = "Select Design Element"; // $NLX-AbstractDesignElementPicker.SelectDesignElement-1$
private AbstractTextEditor _textEditor;
private AbstractComboEditor _comboEditor;
/**
* Default Constructor
*/
public AbstractDesignElementPicker() {
super();
}
/*
* (non-Javadoc)
* @see com.ibm.commons.swt.data.editors.api.AbstractComboEditor#createControl(com.ibm.commons.swt.data.editors.api.CompositeEditor)
*/
@Override
public Control createControl(CompositeEditor parent) {
createSubEditors();
//if we are using a dialog, then we do not want have a combo as well, so return a textEditor
if(useDialog()){
return _textEditor.createControl(parent);
}
//we are not using the dialog, so we use a combo instead with every option listed in the dropdown.
else{
Control control = _comboEditor.createControl(parent);
DesignerDesignElement[] designElements = getDesignElementsForPicker(parent);
if(null != designElements && designElements.length>0){
//create the lookup given the design elements
ILookup lookup = createDesignElementLookup(designElements);
_comboEditor.setLookup(control, lookup);
}
return control;
}
}
/**
* This is a helper method to set up the property editors that we will use.
* They will be either a text editor if we use a button to a pop up a dialog,
* or just a combobox if we are not using the dialog.
*/
private void createSubEditors(){
_textEditor = new AbstractTextEditor() {};
_comboEditor = new AbstractComboEditor(){
/*
* (non-Javadoc)
* @see com.ibm.commons.swt.data.editors.api.AbstractComboEditor#isEditable()
*/
@Override
public boolean isEditable() {
return true;
}
/*
* (non-Javadoc)
* @see com.ibm.commons.swt.data.editors.api.AbstractComboEditor#isFirstBlankLine()
*/
@Override
public boolean isFirstBlankLine() {
return true;
}
};
}
/*
* (non-Javadoc)
* @see com.ibm.commons.swt.data.editors.api.AbstractComboEditor#initControlValue(com.ibm.commons.swt.data.editors.api.CompositeEditor, java.lang.String)
*/
@Override
public void initControlValue(CompositeEditor parent, String value) {
if(useDialog()){
_textEditor.initControlValue(parent, value);
}
else{
_comboEditor.initControlValue(parent, value);
}
}
/*
* (non-Javadoc)
* @see com.ibm.commons.swt.data.editors.api.PropertyEditor#stopEdit(com.ibm.commons.swt.data.editors.api.CompositeEditor)
*/
public boolean stopEdit(CompositeEditor parent) {
if(useDialog()){
return _textEditor.stopEdit(parent);
}
else{
return _comboEditor.stopEdit(parent);
}
}
/*
* (non-Javadoc)
* @see com.ibm.commons.swt.data.editors.api.PropertyEditor#setId(java.lang.String)
*/
public void setId(String id) {
if(useDialog()){
_textEditor.setId(id);
}
else{
_comboEditor.setId(id);
}
}
/**
* This method will take in the DesignerDesignElement IDs that are passed in by getDesignElementIDs,
* and get all the corresponding Design Elements from the Designer Project.
* @param editor
* @return
*/
private DesignerDesignElement[] getDesignElementsForPicker(CompositeEditor editor) {
ArrayList<DesignerDesignElement> designElementList = new ArrayList<DesignerDesignElement>();
DesignerProject project = getDesignerProjectForEditor(editor);
if(null != project){
String[] designElementIds = getDesignElementIDs();
if(null != designElementIds && designElementIds.length>0){
for(int i=0; i<designElementIds.length; i++){
DesignerDesignElement[] designElements = project.getDesignElements(designElementIds[i]);
for(int x=0; x<designElements.length; x++){
designElementList.add(designElements[x]);
}
}
}
}
if(designElementList.size()>0){
DesignerDesignElement[] designElementsArray = designElementList.toArray(new DesignerDesignElement[designElementList.size()]);
if(null != designElementsArray && designElementsArray.length>0){
return designElementsArray;
}
}
return null;
}
/**
* This method will take in a DesignerDesignElement id that is passed in
* and get all the corresponding Design Elements from the Designer Project.
* @param editor
* @param designElementId
* @return
*/
private DesignerDesignElement[] getDesignElementsForID(CompositeEditor editor, String designElementId) {
DesignerProject project = getDesignerProjectForEditor(editor);
if(null != project){
if(StringUtil.isNotEmpty(designElementId)){
DesignerDesignElement[] designElements = project.getDesignElements(designElementId);
if(null != designElements && designElements.length>0){
return designElements;
}
}
}
return null;
}
/**
* This method will get the DesignerProject for the current XPage and return it.
* @param compEditor
* @return
*/
private DesignerProject getDesignerProjectForEditor(CompositeEditor compEditor){
IWorkbenchPart part = super.getWorkBenchPart();
if(part instanceof EditorPart){
EditorPart editor = (EditorPart)part;
IEditorInput input = editor.getEditorInput();
if(input instanceof IFileEditorInput){
IFileEditorInput fileInput = (IFileEditorInput)input;
IFile xpageFile = fileInput.getFile();
if(null != xpageFile){
IProject project = xpageFile.getProject();
if(null != project){
DesignerProject designerProj = DesignerResource.getDesignerProject(project);
if(null != designerProj){
return designerProj;
}
}
}
}
}
return null;
}
/**
* Takes a design element and returns what alias should be displayed.
* By default we display the last alias. If that last alias is the same
* as the design element name, then no alias is returned.
* @param compEditor
* @return
*/
protected String getAliasToDisplay(DesignerDesignElement designElement){
if(null != designElement){
String designElementName = designElement.getName();
String designElementAliasList = designElement.getAlias();
if(StringUtil.isNotEmpty(designElementAliasList)){
designElementName = designElementName.trim();
designElementAliasList = designElementAliasList.trim();
//The element only has one alias and it is the same as the elements name
if(StringUtil.equals(designElementName, designElementAliasList)){
return null;
}
//if there is more than one alias present
int separatorPos = designElementAliasList.lastIndexOf("|");
if(separatorPos !=-1){
//get the last alias. If it does not match the design element name, then that
//is the alias that we should display
separatorPos++;
if(designElementAliasList.length()>separatorPos){
String lastAlias = designElementAliasList.substring(separatorPos);
if(StringUtil.isNotEmpty(lastAlias)){
if(!StringUtil.equals(lastAlias, designElementName)){
return lastAlias;
}
}
}
}
else{
//we only have one alias in the list and it does not match the design element name, so return it.
if(StringUtil.isNotEmpty(designElementAliasList)){
return designElementAliasList;
}
}
}
}
//alias for element
return null;
}
/**
* Abstract method that must be implemented by any class that extends this one.
* The method must return a array of strings, where each of those strings is a
* DesignerDesignElement ID as specified in DesignerResource
*
* A sample method body to create a picker for Form design elements would be
*
* return new String[]{DesignerResource.TYPE_FORM};
*
* @return
*/
protected abstract String[] getDesignElementIDs();
/**
* Abstract method that must be implemented by any class that extends this one.
* The method is passed in a complete list of DesignerDesignElements that
* exist in the Designer Project whose type matches those returned by getDesignElementIDs()
*
* It is up to the implementer to determine what DesignerDesignElement information they
* wish to display in the picker and set in source.
*
* @return
*/
protected abstract StringLookup createDesignElementLookup(DesignerDesignElement[] designElements);
/**
* Subclasses can override this method to change the title of the design element picker dialog.
* This will only have an effect if the subclass is using the dialog.
* @param title
*/
protected String getDialogTitleText(){
return DIALOG_TITLE;
}
/*
* (non-Javadoc)
* @see com.ibm.commons.swt.data.editors.api.PropertyEditor#hasDialogButton(com.ibm.commons.swt.data.editors.api.CompositeEditor)
*/
@Override
public boolean hasDialogButton(CompositeEditor parent) {
return useDialog();
}
/**
* This method is used to determine whether or not a dialog should be used to pick the Design Element.s
* By default the dialog is used if more than one design element id is specified in getDesignElementIDs.
* However a subclass could override this to always use the dialog even if there is only one design
* element specified, or to not use the dialog at all even if there are multiple design elements specified.
* In the case where multiple design elements are specified in getDesignElementIDs and the dialog is not
* used, all the design elements from all design element categories will be add to one combo drop down list.
* @return
*/
protected boolean useDialog(){
if(hasMultipleDesignElements()){
return true;
}
return false;
}
/**
* This method is used to get the labels to display in the combo box of the design element picker dialog.
* If this method is not overriden by subclasses, the design element ids will be displayed.
* @param designElementId
* @return
*/
protected String getLabelForDesignElementID(String designElementId){
return designElementId;
}
/*
* (non-Javadoc)
* @see com.ibm.commons.swt.data.editors.api.PropertyEditor#callDialog(com.ibm.commons.swt.data.editors.api.CompositeEditor, java.lang.String)
*/
@Override
public String callDialog(CompositeEditor parent, String value) {
DesignElementPickerDialog dialog = new DesignElementPickerDialog(parent, value);
int result = dialog.open();
if (result == IDialogConstants.OK_ID) {
return dialog.getValue();
}
return value;
}
/**
* Helper method to check if more than one design element id has been specified by a subclass.
* @return
*/
private boolean hasMultipleDesignElements(){
String[] designElementIds = getDesignElementIDs();
if(designElementIds.length>1){
return true;
}
return false;
}
/**
* This class is the Dialog we pop up to allow users to select options defined in the parameters of this editor.
*/
private class DesignElementPickerDialog extends LWPDNoTitleAreaDialog {
//initial height and width of the dialog when it is popped up
private int INITIAL_DIALOG_HEIGHT = 350;
private int INITIAL_DIALOG_WIDTH = 450;
//the value selected in the design element list.
private String _value;
//a map used to store a mapping from a label for a design element id back to it's design element id.
private HashMap<String,String> _labelToDesElemIdMap = new HashMap<String, String>();
//store off design elements so we don't recreate them all the time
private HashMap<String,DesignerDesignElement[]> _idToDesignElementsMap = new HashMap<String,DesignerDesignElement[]>();
private DesignerDesignElement[] _designElementsCache = null;
//the currently selected design element id in the combo.
private String _selectedDesignElementId;
//Dialog UI elements
private CompositeEditor _editorParent;
private CustomCombo _combo;
private CustomTable _designElementsTable;
//string used to store a value in the data of a TableItem
private final String TABLE_ITEM_DATA_VALUE_STRING = "selectedValue"; // $NON-NLS-1$
/**
* Default dialog constructor
* @param parent
* @param value - the current value set in source
*/
public DesignElementPickerDialog(CompositeEditor parent, String value){
super(parent.getShell());
//make the dialog resizable
setShellStyle(getShellStyle() | SWT.RESIZE);
_value = value;
_editorParent = parent;
}
/*
* (non-Javadoc)
* @see com.ibm.commons.swt.data.dialog.LWPDCommonDialog#fillClientArea(org.eclipse.swt.widgets.Composite)
*/
@Override
protected void fillClientArea(Composite parent) {
//change the parents layout to only have one column instead of two
Object layoutDataObj = parent.getLayout();
if(layoutDataObj instanceof GridLayout){
GridLayout layout = (GridLayout)layoutDataObj;
layout.numColumns = 1;
}
//crate a comboBox to select a design element type if more than one is specified
if(hasMultipleDesignElements()){
createDesignElementPickerCombo(parent);
}
//create a table that will contain all the design elements
createDesignElementsTable(parent);
populateDesignElementsTable();
//set initial selection in the combo and update the table contents.
if(null != _combo){
if(_combo.getItemCount()>0){
_combo.select(0);
String comboValue = _combo.getItem(0);
if(StringUtil.isNotEmpty(comboValue)){
String selectedDesignElementId = _labelToDesElemIdMap.get(comboValue);
if(StringUtil.isNotEmpty(selectedDesignElementId)){
_selectedDesignElementId = selectedDesignElementId;
refreshTableContents();
}
}
}
}
}
/**
* Helper method to create the design element type comboBox in the Design Element Picker Dialog.
* @param parent
* @return
*/
private CustomCombo createDesignElementPickerCombo(Composite parent){
//create the read only combo
_combo = new CustomCombo(parent, SWT.READ_ONLY, "com.ibm.extlib.tooling.DesignElementPickerCombo.id"); // $NON-NLS-1$
_combo.setLayoutData(SWTLayoutUtils.createGDFillHorizontal());
//get all the design element ids specified by the subclass and add them to the combo
String[] designElementIDs = getDesignElementIDs();
for(int i=0; i<designElementIDs.length; i++){
String designElementId = designElementIDs[i];
if(StringUtil.isNotEmpty(designElementId)){
String label = getLabelForDesignElementID(designElementId);
if(StringUtil.isNotEmpty(label)){
//add label and id to a map, so that when a user selects a label, we know which id they are actually selecting.
_labelToDesElemIdMap.put(label, designElementId);
_combo.add(label);
}
}
}
//add a selection change listener to update the table of design elements.
_combo.addSelectionListener(new SelectionListener() {
public void widgetSelected(SelectionEvent selectionEvent) {
Object source = selectionEvent.getSource();
if(source instanceof CustomCombo){
//get the combo instance from the events
CustomCombo comboBox = (CustomCombo)source;
String comboValue = "";
//get the selected option, and get it's label
int selectedIndex = comboBox.getSelectionIndex();
if(selectedIndex>-1){
comboValue = comboBox.getItem(selectedIndex);
}
//if we have the label, find the design element id that it maps to and set that as the selected design element id.
//call an update on the table of design elements.
if(StringUtil.isNotEmpty(comboValue)){
String selectedDesignElementId = _labelToDesElemIdMap.get(comboValue);
if(StringUtil.isNotEmpty(selectedDesignElementId)){
_selectedDesignElementId = selectedDesignElementId;
refreshTableContents();
}
}
}
}
public void widgetDefaultSelected(SelectionEvent selectionEvent) {
//do nothing
}
});
return _combo;
}
/**
* Create the table that will contain the design elements of a given type.
* @param parentComp
*/
private void createDesignElementsTable(Composite parentComp){
//create the table object
_designElementsTable = new CustomTable(parentComp, SWT.V_SCROLL|SWT.H_SCROLL| SWT.BORDER | SWT.FULL_SELECTION, "PagerBasicsPanelOptionsTable.id"); // $NON-NLS-1$
GridData gd = SWTLayoutUtils.createGDFillHorizontal();
_designElementsTable.setLayoutData(gd);
_designElementsTable.setRows(8);
//add a selection listener to update the selected value in the dialog
_designElementsTable.addSelectionListener(new SelectionListener(){
public void widgetDefaultSelected(SelectionEvent arg0) {
//do nothing
}
public void widgetSelected(SelectionEvent arg0) {
//the source of the even is going to be the table, so we can just use
//the reference we already have to get the selection
TableItem[] selectedItems = _designElementsTable.getSelection();
if(null != selectedItems && selectedItems.length==1){
TableItem selectedItem = selectedItems[0];
//we store the value to be set in source in the data of the tableItem.
//So get that value out of the data and set it as the selected value of the dialog.
Object valueObj = selectedItem.getData(TABLE_ITEM_DATA_VALUE_STRING);
if(valueObj instanceof String){
String selectedValue = (String)valueObj;
if(StringUtil.isNotEmpty(selectedValue)){
_value=selectedValue;
}
}
}
}
});
}
/**
* Helper method to fill the table of design elements with a tableItem for every design element.
*/
private void populateDesignElementsTable(){
DesignerDesignElement[] designElements = null;
if(null != _designElementsTable){
//if we are using a combo, then we only want to populated the table with design elements of the type
//selected in the combo box.
if(null != _combo){
//try to find the design element array in our cache first. Create cache if not there already.
if(StringUtil.isNotEmpty(_selectedDesignElementId)){
if(_idToDesignElementsMap.containsKey(_selectedDesignElementId)){
designElements = _idToDesignElementsMap.get(_selectedDesignElementId);
}
else{
designElements = getDesignElementsForID(_editorParent, _selectedDesignElementId);
_idToDesignElementsMap.put(_selectedDesignElementId, designElements);
}
}
}
//if we are not using a combo then we just populate the table with all the design elements for all
//design element type ids. Again check for cached version first.
else{
if(null != _designElementsCache){
designElements = _designElementsCache;
}
else{
designElements = getDesignElementsForPicker(_editorParent);
_designElementsCache = designElements;
}
}
//call to our subclass to create the design element lookup for the design elements.
//This allows the subclass to decide how to display the design elements and what value
//the need to set in source. Use this information to create the table items.
//Store the value to be set in source for a given label into the data of the tableItem.
if(null != designElements && designElements.length>0){
StringLookup designElementLookup = createDesignElementLookup(designElements);
if(null != designElementLookup){
for(int i=0; i<designElementLookup.size(); i++){
createTableItem(_designElementsTable, designElementLookup.getLabel(i), designElementLookup.getCode(i));
}
}
}
//check the tableItems that we have created. If one of the them is the currently set value in source,
//then select it.
TableItem[] items = _designElementsTable.getItems();
if(null != items && items.length>0){
for(int i=0; i<items.length; i++){
TableItem item = items[i];
Object valueObj = item.getData(TABLE_ITEM_DATA_VALUE_STRING);
if(valueObj instanceof String){
String selectedValue = (String)valueObj;
if(StringUtil.isNotEmpty(selectedValue) && StringUtil.isNotEmpty(_value) && StringUtil.equals(selectedValue, _value)){
_designElementsTable.setSelection(item);
break;
}
}
}
}
}
}
/**
* Helper method to remove all the tableItems for the design element table, and repopulate it with
* design elements of the type specified in the combo box.
*/
private void refreshTableContents(){
if(null != _designElementsTable){
_designElementsTable.removeAll();
populateDesignElementsTable();
_designElementsTable.layout();
}
}
/**
* Helper to create a TableItem used to represent a design element in the table of design elements.
*
* @param parent - the parent table to add the tableItem to
* @param label - the label to display in the table
* @param value - the value to set in source, if this tableItem is selected.
* @return
*/
private TableItem createTableItem(Table parent, String label, String value){
TableItem tableItem = new TableItem(parent, SWT.NONE); // $NON-NLS-1$
tableItem.setText(label);
tableItem.setData(TABLE_ITEM_DATA_VALUE_STRING, value);
return tableItem;
}
/*
* (non-Javadoc)
* @see com.ibm.commons.swt.data.dialog.LWPDCommonDialog#performDialogOperation(org.eclipse.core.runtime.IProgressMonitor)
*/
@Override
protected boolean performDialogOperation(IProgressMonitor progressMonitor) {
return true;
}
/**
* Gets the value that has been selected in the dialog
* @return
*/
public String getValue(){
return _value;
}
/*
* (non-Javadoc)
* @see com.ibm.commons.swt.data.dialog.LWPDCommonDialog#getDialogTitle()
*/
protected String getDialogTitle(){
return getDialogTitleText();
}
/*
* (non-Javadoc)
* @see org.eclipse.jface.dialogs.TitleAreaDialog#getInitialSize()
*/
@Override
protected Point getInitialSize() {
return new Point(INITIAL_DIALOG_WIDTH, INITIAL_DIALOG_HEIGHT);
}
}
}