--- /dev/null
+package org.argeo.cms.forms;
+
+import javax.jcr.Node;
+import javax.jcr.RepositoryException;
+
+import org.argeo.cms.viewers.EditablePart;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Control;
+import org.eclipse.swt.widgets.Label;
+import org.eclipse.swt.widgets.Text;
+
+/** Editable String that displays a browsable link when read-only */
+public class EditableLink extends EditablePropertyString implements
+ EditablePart {
+ private static final long serialVersionUID = 5055000749992803591L;
+
+ private String type;
+ private String message;
+ private boolean readOnly;
+
+ public EditableLink(Composite parent, int style, Node node,
+ String propertyName, String type, String message)
+ throws RepositoryException {
+ super(parent, style, node, propertyName, message);
+ this.message = message;
+ this.type = type;
+
+ readOnly = SWT.READ_ONLY == (style & SWT.READ_ONLY);
+ if (node.hasProperty(propertyName)) {
+ this.setStyle(FormStyle.propertyText.style());
+ this.setText(node.getProperty(propertyName).getString());
+ } else {
+ this.setStyle(FormStyle.propertyMessage.style());
+ this.setText("");
+ }
+ }
+
+ public void setText(String text) {
+ Control child = getControl();
+ if (child instanceof Label) {
+ Label lbl = (Label) child;
+ if (FormUtils.notEmpty(text))
+ lbl.setText(message);
+ else if (readOnly)
+ setLinkValue(lbl, text);
+ else
+ // if canEdit() we put only the value with no link
+ // to avoid glitches of the edition life cycle
+ lbl.setText(text);
+ } else if (child instanceof Text) {
+ Text txt = (Text) child;
+ if (FormUtils.notEmpty(text)) {
+ txt.setText("");
+ txt.setMessage(message);
+ } else
+ txt.setText(text);
+ }
+ }
+
+ private void setLinkValue(Label lbl, String text) {
+ if (FormStyle.email.style().equals(type))
+ lbl.setText(FormUtils.getMailLink(text));
+ else if (FormStyle.phone.style().equals(type))
+ lbl.setText(FormUtils.getPhoneLink(text));
+ else if (FormStyle.website.style().equals(type))
+ lbl.setText(FormUtils.getUrlLink(text));
+ else if (FormStyle.facebook.style().equals(type)
+ || FormStyle.instagram.style().equals(type)
+ || FormStyle.linkedIn.style().equals(type)
+ || FormStyle.twitter.style().equals(type))
+ lbl.setText(FormUtils.getUrlLink(text));
+ }
+}
\ No newline at end of file
--- /dev/null
+package org.argeo.cms.forms;
+
+import java.util.List;
+
+import javax.jcr.Node;
+import javax.jcr.RepositoryException;
+
+import org.argeo.cms.util.CmsUtils;
+import org.argeo.cms.viewers.EditablePart;
+import org.argeo.cms.widgets.StyledControl;
+import org.argeo.eclipse.ui.EclipseUiUtils;
+import org.eclipse.jface.dialogs.MessageDialog;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.events.SelectionListener;
+import org.eclipse.swt.events.TraverseEvent;
+import org.eclipse.swt.events.TraverseListener;
+import org.eclipse.swt.layout.GridData;
+import org.eclipse.swt.layout.GridLayout;
+import org.eclipse.swt.layout.RowLayout;
+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.Text;
+
+/** Display, add or remove values from a list in a CMS context */
+public class EditableMultiStringProperty extends StyledControl implements
+ EditablePart {
+ private static final long serialVersionUID = -7044614381252178595L;
+
+ private String propertyName;
+ private String message;
+ // TODO implement the ability to provide a list of legal values
+ private String[] possibleValues;
+ private boolean canEdit;
+ private SelectionListener removeValueSL;
+ private List<String> values;
+
+ // TODO manage within the CSS
+ private int rowSpacing = 5;
+ private int rowMarging = 0;
+ private int oneValueMargingRight = 5;
+ private int btnWidth = 16;
+ private int btnHeight = 16;
+ private int btnHorizontalIndent = 3;
+
+ public EditableMultiStringProperty(Composite parent, int style, Node node,
+ String propertyName, List<String> values, String[] possibleValues,
+ String addValueMsg, SelectionListener removeValueSelectionListener)
+ throws RepositoryException {
+ super(parent, style, node, true);
+
+ this.propertyName = propertyName;
+ this.values = values;
+ this.possibleValues = possibleValues;
+ this.message = addValueMsg;
+ this.canEdit = removeValueSelectionListener != null;
+ this.removeValueSL = removeValueSelectionListener;
+ }
+
+ public List<String> getValues() {
+ return values;
+ }
+
+ public void setValues(List<String> values) {
+ this.values = values;
+ }
+
+ // Row layout items do not need explicit layout data
+ protected void setControlLayoutData(Control control) {
+ }
+
+ /** To be overridden */
+ protected void setContainerLayoutData(Composite composite) {
+ composite.setLayoutData(CmsUtils.fillWidth());
+ }
+
+ @Override
+ public Control getControl() {
+ return super.getControl();
+ }
+
+ @Override
+ protected Control createControl(Composite box, String style) {
+ Composite row = new Composite(box, SWT.NO_FOCUS);
+ row.setLayoutData(EclipseUiUtils.fillAll());
+
+ RowLayout rl = new RowLayout(SWT.HORIZONTAL);
+ rl.wrap = true;
+ rl.spacing = rowSpacing;
+ rl.marginRight = rl.marginLeft = rl.marginBottom = rl.marginTop = rowMarging;
+ row.setLayout(rl);
+
+ if (values != null) {
+ for (final String value : values) {
+ if (canEdit)
+ createRemovableValue(row, SWT.SINGLE, value);
+ else
+ createValueLabel(row, SWT.SINGLE, value);
+ }
+ }
+
+ if (!canEdit)
+ return row;
+ else if (isEditing())
+ return createText(row, style);
+ else
+ return createLabel(row, style);
+ }
+
+ /**
+ * Override to provide specific layout for the existing values, typically
+ * adding a pound (#) char for tags or anchor info for browsable links. We
+ * assume the parent composite already has a layout and it is the caller
+ * responsibility to apply corresponding layout data
+ */
+ protected Label createValueLabel(Composite parent, int style, String value) {
+ Label label = new Label(parent, style);
+ label.setText("#" + value);
+ CmsUtils.markup(label);
+ CmsUtils.style(label, FormStyle.propertyText.style());
+ return label;
+ }
+
+ private Composite createRemovableValue(Composite parent, int style,
+ String value) {
+ Composite valCmp = new Composite(parent, SWT.NO_FOCUS);
+ GridLayout gl = EclipseUiUtils.noSpaceGridLayout(new GridLayout(2,
+ false));
+ gl.marginRight = oneValueMargingRight;
+ valCmp.setLayout(gl);
+
+ createValueLabel(valCmp, SWT.WRAP, value);
+
+ Button deleteBtn = new Button(valCmp, SWT.FLAT);
+ deleteBtn.setData(FormConstants.LINKED_VALUE, value);
+ deleteBtn.addSelectionListener(removeValueSL);
+ CmsUtils.style(deleteBtn, FormStyle.delete.style()
+ + FormStyle.BUTTON_SUFFIX);
+ GridData gd = new GridData();
+ gd.heightHint = btnHeight;
+ gd.widthHint = btnWidth;
+ gd.horizontalIndent = btnHorizontalIndent;
+ deleteBtn.setLayoutData(gd);
+
+ return valCmp;
+ }
+
+ protected Text createText(Composite box, String style) {
+ final Text text = new Text(box, getStyle());
+ // The "add new value" text is not meant to change, so we can set it on
+ // creation
+ text.setMessage(message);
+ CmsUtils.style(text, style);
+ text.setFocus();
+
+ text.addTraverseListener(new TraverseListener() {
+ private static final long serialVersionUID = 1L;
+
+ public void keyTraversed(TraverseEvent e) {
+ if (e.keyCode == SWT.CR) {
+ addValue(text);
+ e.doit = false;
+ }
+ }
+ });
+
+ // The OK button does not work with the focusOut listener
+ // because focus out is called before the OK button is pressed
+
+ // // we must call layout() now so that the row data can compute the
+ // height
+ // // of the other controls.
+ // text.getParent().layout();
+ // int height = text.getSize().y;
+ //
+ // Button okBtn = new Button(box, SWT.BORDER | SWT.PUSH | SWT.BOTTOM);
+ // okBtn.setText("OK");
+ // RowData rd = new RowData(SWT.DEFAULT, height - 2);
+ // okBtn.setLayoutData(rd);
+ //
+ // okBtn.addSelectionListener(new SelectionAdapter() {
+ // private static final long serialVersionUID = 2780819012423622369L;
+ //
+ // @Override
+ // public void widgetSelected(SelectionEvent e) {
+ // addValue(text);
+ // }
+ // });
+
+ return text;
+ }
+
+ /** Performs the real addition, overwrite to make further sanity checks */
+ protected void addValue(Text text) {
+ String value = text.getText();
+ String errMsg = null;
+
+ if (FormUtils.notEmpty(value))
+ return;
+
+ if (values.contains(value))
+ errMsg = "Dupplicated value: " + value
+ + ", please correct and try again";
+ if (errMsg != null)
+ MessageDialog.openError(this.getShell(), "Addition not allowed",
+ errMsg);
+ else {
+ values.add(value);
+ Composite newCmp = createRemovableValue(text.getParent(),
+ SWT.SINGLE, value);
+ newCmp.moveAbove(text);
+ text.setText("");
+ newCmp.getParent().layout();
+ }
+ }
+
+ protected Label createLabel(Composite box, String style) {
+ if (canEdit) {
+ Label lbl = new Label(box, getStyle());
+ lbl.setText(message);
+ CmsUtils.style(lbl, style);
+ CmsUtils.markup(lbl);
+ if (mouseListener != null)
+ lbl.addMouseListener(mouseListener);
+ return lbl;
+ }
+ return null;
+ }
+
+ protected void clear(boolean deep) {
+ Control child = getControl();
+ if (deep)
+ super.clear(deep);
+ else {
+ child.getParent().dispose();
+ }
+ }
+
+ public void setText(String text) {
+ Control child = getControl();
+ if (child instanceof Label) {
+ Label lbl = (Label) child;
+ if (canEdit)
+ lbl.setText(text);
+ else
+ lbl.setText("");
+ } else if (child instanceof Text) {
+ Text txt = (Text) child;
+ txt.setText(text);
+ }
+ }
+
+ public synchronized void startEditing() {
+ getControl().setData(STYLE, FormStyle.propertyText.style());
+ super.startEditing();
+ }
+
+ public synchronized void stopEditing() {
+ getControl().setData(STYLE, FormStyle.propertyMessage.style());
+ super.stopEditing();
+ }
+
+ public String getPropertyName() {
+ return propertyName;
+ }
+}
\ No newline at end of file
--- /dev/null
+package org.argeo.cms.forms;
+
+import java.text.DateFormat;
+import java.util.Calendar;
+import java.util.GregorianCalendar;
+
+import javax.jcr.Node;
+import javax.jcr.RepositoryException;
+
+import org.argeo.cms.util.CmsUtils;
+import org.argeo.cms.viewers.EditablePart;
+import org.argeo.cms.widgets.StyledControl;
+import org.argeo.eclipse.ui.EclipseUiUtils;
+import org.eclipse.swt.SWT;
+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.ShellAdapter;
+import org.eclipse.swt.events.ShellEvent;
+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.DateTime;
+import org.eclipse.swt.widgets.Label;
+import org.eclipse.swt.widgets.Shell;
+import org.eclipse.swt.widgets.Text;
+
+/** CMS form part to display and edit a date */
+public class EditablePropertyDate extends StyledControl implements EditablePart {
+ private static final long serialVersionUID = 2500215515778162468L;
+
+ // Context
+ private String propertyName;
+ private String message;
+ private DateFormat dateFormat;
+
+ // UI Objects
+ private Text dateTxt;
+ private Button openCalBtn;
+
+ // TODO manage within the CSS
+ private int fieldBtnSpacing = 5;
+
+ /**
+ *
+ * @param parent
+ * @param style
+ * @param node
+ * @param propertyName
+ * @param message
+ * @param dateFormat
+ * provide a {@link DateFormat} as contract to be able to
+ * read/write dates as strings
+ * @throws RepositoryException
+ */
+ public EditablePropertyDate(Composite parent, int style, Node node,
+ String propertyName, String message, DateFormat dateFormat)
+ throws RepositoryException {
+ super(parent, style, node, false);
+
+ this.propertyName = propertyName;
+ this.message = message;
+ this.dateFormat = dateFormat;
+
+ if (node.hasProperty(propertyName)) {
+ this.setStyle(FormStyle.propertyText.style());
+ this.setText(dateFormat.format(node.getProperty(propertyName)
+ .getDate().getTime()));
+ } else {
+ this.setStyle(FormStyle.propertyMessage.style());
+ this.setText(message);
+ }
+ }
+
+ public void setText(String text) {
+ Control child = getControl();
+ if (child instanceof Label) {
+ Label lbl = (Label) child;
+ if (FormUtils.notEmpty(text))
+ lbl.setText(message);
+ else
+ lbl.setText(text);
+ } else if (child instanceof Text) {
+ Text txt = (Text) child;
+ if (FormUtils.notEmpty(text)) {
+ txt.setText("");
+ } else
+ txt.setText(text);
+ }
+ }
+
+ public synchronized void startEditing() {
+ // if (dateTxt != null && !dateTxt.isDisposed())
+ getControl().setData(STYLE, FormStyle.propertyText.style());
+ super.startEditing();
+ }
+
+ public synchronized void stopEditing() {
+ if (FormUtils.notEmpty(dateTxt.getText()))
+ getControl().setData(STYLE, FormStyle.propertyMessage.style());
+ else
+ getControl().setData(STYLE, FormStyle.propertyText.style());
+ super.stopEditing();
+ }
+
+ public String getPropertyName() {
+ return propertyName;
+ }
+
+ @Override
+ protected Control createControl(Composite box, String style) {
+ if (isEditing()) {
+ return createCustomEditableControl(box, style);
+ } else
+ return createLabel(box, style);
+ }
+
+ protected Label createLabel(Composite box, String style) {
+ Label lbl = new Label(box, getStyle() | SWT.WRAP);
+ lbl.setLayoutData(CmsUtils.fillWidth());
+ CmsUtils.style(lbl, style);
+ CmsUtils.markup(lbl);
+ if (mouseListener != null)
+ lbl.addMouseListener(mouseListener);
+ return lbl;
+ }
+
+ private Control createCustomEditableControl(Composite box, String style) {
+ box.setLayoutData(CmsUtils.fillWidth());
+ Composite dateComposite = new Composite(box, SWT.NONE);
+ GridLayout gl = EclipseUiUtils.noSpaceGridLayout(new GridLayout(2,
+ false));
+ gl.horizontalSpacing = fieldBtnSpacing;
+ dateComposite.setLayout(gl);
+ dateTxt = new Text(dateComposite, SWT.BORDER);
+ CmsUtils.style(dateTxt, style);
+ dateTxt.setLayoutData(new GridData(120, SWT.DEFAULT));
+ dateTxt.setToolTipText("Enter a date with form \""
+ + FormUtils.DEFAULT_SHORT_DATE_FORMAT
+ + "\" or use the calendar");
+ openCalBtn = new Button(dateComposite, SWT.FLAT);
+ CmsUtils.style(openCalBtn, FormStyle.calendar.style()
+ + FormStyle.BUTTON_SUFFIX);
+ GridData gd = new GridData(SWT.CENTER, SWT.CENTER, false, false);
+ gd.heightHint = 17;
+ openCalBtn.setLayoutData(gd);
+ // openCalBtn.setImage(PeopleRapImages.CALENDAR_BTN);
+
+ openCalBtn.addSelectionListener(new SelectionAdapter() {
+ private static final long serialVersionUID = 1L;
+
+ public void widgetSelected(SelectionEvent event) {
+ CalendarPopup popup = new CalendarPopup(dateTxt);
+ popup.open();
+ }
+ });
+
+ // dateTxt.addFocusListener(new FocusListener() {
+ // private static final long serialVersionUID = 1L;
+ //
+ // @Override
+ // public void focusLost(FocusEvent event) {
+ // String newVal = dateTxt.getText();
+ // // Enable reset of the field
+ // if (FormUtils.notNull(newVal))
+ // calendar = null;
+ // else {
+ // try {
+ // Calendar newCal = parseDate(newVal);
+ // // DateText.this.setText(newCal);
+ // calendar = newCal;
+ // } catch (ParseException pe) {
+ // // Silent. Manage error popup?
+ // if (calendar != null)
+ // EditablePropertyDate.this.setText(calendar);
+ // }
+ // }
+ // }
+ //
+ // @Override
+ // public void focusGained(FocusEvent event) {
+ // }
+ // });
+ return dateTxt;
+ }
+
+ protected void clear(boolean deep) {
+ Control child = getControl();
+ if (deep || child instanceof Label)
+ super.clear(deep);
+ else {
+ child.getParent().dispose();
+ }
+ }
+
+ /** Enable setting a custom tooltip on the underlying text */
+ @Deprecated
+ public void setToolTipText(String toolTipText) {
+ dateTxt.setToolTipText(toolTipText);
+ }
+
+ @Deprecated
+ /** Enable setting a custom message on the underlying text */
+ public void setMessage(String message) {
+ dateTxt.setMessage(message);
+ }
+
+ @Deprecated
+ public void setText(Calendar cal) {
+ String newValueStr = "";
+ if (cal != null)
+ newValueStr = dateFormat.format(cal.getTime());
+ if (!newValueStr.equals(dateTxt.getText()))
+ dateTxt.setText(newValueStr);
+ }
+
+ // UTILITIES TO MANAGE THE CALENDAR POPUP
+ // TODO manage the popup shell in a cleaner way
+ private class CalendarPopup extends Shell {
+ private static final long serialVersionUID = 1L;
+ private DateTime dateTimeCtl;
+
+ public CalendarPopup(Control source) {
+ super(source.getDisplay(), SWT.NO_TRIM | SWT.BORDER | SWT.ON_TOP);
+ populate();
+ // Add border and shadow style
+ CmsUtils.markup(CalendarPopup.this);
+ CmsUtils.style(CalendarPopup.this, FormStyle.popupCalendar.style());
+ pack();
+ layout();
+ setLocation(source.toDisplay((source.getLocation().x - 2),
+ (source.getSize().y) + 3));
+
+ addShellListener(new ShellAdapter() {
+ private static final long serialVersionUID = 5178980294808435833L;
+
+ @Override
+ public void shellDeactivated(ShellEvent e) {
+ close();
+ dispose();
+ }
+ });
+ open();
+ }
+
+ private void setProperty() {
+ // Direct set does not seems to work. investigate
+ // cal.set(dateTimeCtl.getYear(), dateTimeCtl.getMonth(),
+ // dateTimeCtl.getDay(), 12, 0);
+ Calendar cal = new GregorianCalendar();
+ cal.set(Calendar.YEAR, dateTimeCtl.getYear());
+ cal.set(Calendar.MONTH, dateTimeCtl.getMonth());
+ cal.set(Calendar.DAY_OF_MONTH, dateTimeCtl.getDay());
+ String dateStr = dateFormat.format(cal.getTime());
+ dateTxt.setText(dateStr);
+ }
+
+ protected void populate() {
+ setLayout(EclipseUiUtils.noSpaceGridLayout());
+
+ dateTimeCtl = new DateTime(this, SWT.CALENDAR);
+ dateTimeCtl.setLayoutData(EclipseUiUtils.fillAll());
+
+ Calendar calendar = FormUtils.parseDate(dateFormat,
+ dateTxt.getText());
+
+ if (calendar != null)
+ dateTimeCtl.setDate(calendar.get(Calendar.YEAR),
+ calendar.get(Calendar.MONTH),
+ calendar.get(Calendar.DAY_OF_MONTH));
+
+ dateTimeCtl.addSelectionListener(new SelectionAdapter() {
+ private static final long serialVersionUID = -8414377364434281112L;
+
+ @Override
+ public void widgetSelected(SelectionEvent e) {
+ setProperty();
+ }
+ });
+
+ dateTimeCtl.addMouseListener(new MouseListener() {
+ private static final long serialVersionUID = 1L;
+
+ @Override
+ public void mouseUp(MouseEvent e) {
+ }
+
+ @Override
+ public void mouseDown(MouseEvent e) {
+ }
+
+ @Override
+ public void mouseDoubleClick(MouseEvent e) {
+ setProperty();
+ close();
+ dispose();
+ }
+ });
+ }
+ }
+}
\ No newline at end of file
--- /dev/null
+package org.argeo.cms.forms;
+
+import static org.argeo.cms.forms.FormStyle.propertyMessage;
+import static org.argeo.cms.forms.FormStyle.propertyText;
+
+import javax.jcr.Node;
+import javax.jcr.RepositoryException;
+
+import org.argeo.cms.viewers.EditablePart;
+import org.argeo.cms.widgets.EditableText;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Control;
+import org.eclipse.swt.widgets.Label;
+import org.eclipse.swt.widgets.Text;
+
+/** Editable String in a CMS context */
+public class EditablePropertyString extends EditableText implements
+ EditablePart {
+ private static final long serialVersionUID = 5055000749992803591L;
+
+ private String propertyName;
+ private String message;
+
+ public EditablePropertyString(Composite parent, int style, Node node,
+ String propertyName, String message) throws RepositoryException {
+ super(parent, style, node, true);
+
+ this.propertyName = propertyName;
+ this.message = message;
+
+ if (node.hasProperty(propertyName)) {
+ this.setStyle(propertyText.style());
+ this.setText(node.getProperty(propertyName).getString());
+ } else {
+ this.setStyle(propertyMessage.style());
+ this.setText(message + " ");
+ }
+ }
+
+ public void setText(String text) {
+ Control child = getControl();
+ if (child instanceof Label) {
+ Label lbl = (Label) child;
+ if (FormUtils.notEmpty(text))
+ lbl.setText(message + " ");
+ else
+ lbl.setText(text);
+ } else if (child instanceof Text) {
+ Text txt = (Text) child;
+ if (FormUtils.notEmpty(text)) {
+ txt.setText("");
+ txt.setMessage(message + " ");
+ } else
+ txt.setText(text.replaceAll("<br/>", "\n"));
+ }
+ }
+
+ public synchronized void startEditing() {
+ getControl().setData(STYLE, propertyText.style());
+ super.startEditing();
+ }
+
+ public synchronized void stopEditing() {
+ if (FormUtils.notEmpty(((Text) getControl()).getText()))
+ getControl().setData(STYLE, propertyMessage.style());
+ else
+ getControl().setData(STYLE, propertyText.style());
+ super.stopEditing();
+ }
+
+ public String getPropertyName() {
+ return propertyName;
+ }
+}
\ No newline at end of file
--- /dev/null
+package org.argeo.cms.forms;
+
+/** Constants used in the various CMS Forms */
+public interface FormConstants {
+ // DATAKEYS
+ public final static String LINKED_VALUE = "LinkedValue";
+}
--- /dev/null
+package org.argeo.cms.forms;
+
+import java.util.Observable;
+import java.util.Observer;
+
+import javax.jcr.Node;
+
+import org.argeo.cms.CmsEditable;
+import org.argeo.cms.util.CmsUtils;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.events.SelectionEvent;
+import org.eclipse.swt.events.SelectionListener;
+import org.eclipse.swt.widgets.Button;
+import org.eclipse.swt.widgets.Composite;
+
+/** Add life cycle management abilities to an editable form page */
+public class FormEditorHeader implements SelectionListener, Observer {
+ private static final long serialVersionUID = 7392898696542484282L;
+
+ // private final Node context;
+ private final CmsEditable cmsEditable;
+ private Button publishBtn;
+
+ // Should we provide here the ability to switch from read only to edition
+ // mode?
+ // private Button editBtn;
+ // private boolean readOnly;
+
+ // TODO add information about the current node status, typically if it is
+ // dirty or not
+
+ private Composite parent;
+ private Composite display;
+ private Object layoutData;
+
+ public FormEditorHeader(Composite parent, int style, Node context,
+ CmsEditable cmsEditable) {
+ this.cmsEditable = cmsEditable;
+ this.parent = parent;
+ // readOnly = SWT.READ_ONLY == (style & SWT.READ_ONLY);
+ // this.context = context;
+ if (this.cmsEditable instanceof Observable)
+ ((Observable) this.cmsEditable).addObserver(this);
+ refresh();
+ }
+
+ public void setLayoutData(Object layoutData) {
+ this.layoutData = layoutData;
+ if (display != null && !display.isDisposed())
+ display.setLayoutData(layoutData);
+ }
+
+ protected void refresh() {
+ if (display != null && !display.isDisposed())
+ display.dispose();
+
+ display = new Composite(parent, SWT.NONE);
+ display.setLayoutData(layoutData);
+
+ CmsUtils.style(display, FormStyle.header.style());
+ display.setBackgroundMode(SWT.INHERIT_FORCE);
+
+ display.setLayout(CmsUtils.noSpaceGridLayout());
+
+ publishBtn = createSimpleBtn(display, getPublishButtonLabel());
+ display.moveAbove(null);
+ parent.layout();
+ }
+
+ private Button createSimpleBtn(Composite parent, String label) {
+ Button button = new Button(parent, SWT.FLAT | SWT.PUSH);
+ button.setText(label);
+ CmsUtils.style(button, FormStyle.header.style());
+ button.addSelectionListener(this);
+ return button;
+ }
+
+ private String getPublishButtonLabel() {
+ // Rather check if the current node differs from what has been
+ // previously committed
+ // For the time being, we always reach here, the underlying CmsEditable
+ // is always editing.
+ if (cmsEditable.isEditing())
+ return " Publish ";
+ else
+ return " Edit ";
+ }
+
+ @Override
+ public void widgetSelected(SelectionEvent e) {
+ if (e.getSource() == publishBtn) {
+ // For the time being, the underlying CmsEditable
+ // is always editing when we reach this point
+ if (cmsEditable.isEditing()) {
+ // we always leave the node in a check outed state
+ cmsEditable.stopEditing();
+ cmsEditable.startEditing();
+ } else {
+ cmsEditable.startEditing();
+ }
+ }
+ }
+
+ @Override
+ public void widgetDefaultSelected(SelectionEvent e) {
+ }
+
+ @Override
+ public void update(Observable o, Object arg) {
+ if (o == cmsEditable) {
+ refresh();
+ }
+ }
+}
\ No newline at end of file
--- /dev/null
+package org.argeo.cms.forms;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.text.DateFormat;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.List;
+
+import javax.jcr.Node;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+import javax.jcr.Value;
+import javax.jcr.ValueFormatException;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.argeo.ArgeoException;
+import org.argeo.cms.CmsEditable;
+import org.argeo.cms.CmsException;
+import org.argeo.cms.CmsImageManager;
+import org.argeo.cms.CmsNames;
+import org.argeo.cms.internal.text.MarkupValidatorCopy;
+import org.argeo.cms.text.Img;
+import org.argeo.cms.util.CmsUtils;
+import org.argeo.cms.viewers.AbstractPageViewer;
+import org.argeo.cms.viewers.EditablePart;
+import org.argeo.cms.viewers.Section;
+import org.argeo.cms.viewers.SectionPart;
+import org.argeo.cms.widgets.EditableImage;
+import org.argeo.cms.widgets.StyledControl;
+import org.argeo.eclipse.ui.EclipseUiUtils;
+import org.argeo.jcr.JcrUtils;
+import org.eclipse.jface.dialogs.MessageDialog;
+import org.eclipse.rap.addons.fileupload.FileDetails;
+import org.eclipse.rap.addons.fileupload.FileUploadEvent;
+import org.eclipse.rap.addons.fileupload.FileUploadHandler;
+import org.eclipse.rap.addons.fileupload.FileUploadListener;
+import org.eclipse.rap.addons.fileupload.FileUploadReceiver;
+import org.eclipse.rap.rwt.service.ServerPushSession;
+import org.eclipse.rap.rwt.widgets.FileUpload;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.events.FocusEvent;
+import org.eclipse.swt.events.FocusListener;
+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.SelectionListener;
+import org.eclipse.swt.graphics.Point;
+import org.eclipse.swt.layout.FormAttachment;
+import org.eclipse.swt.layout.FormData;
+import org.eclipse.swt.layout.FormLayout;
+import org.eclipse.swt.layout.GridData;
+import org.eclipse.swt.layout.GridLayout;
+import org.eclipse.swt.layout.RowLayout;
+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.Text;
+
+/** Manage life cycle of a form page that is linked to a given node */
+public class FormPageViewer extends AbstractPageViewer {
+ private final static Log log = LogFactory.getLog(FormPageViewer.class);
+ private static final long serialVersionUID = 5277789504209413500L;
+
+ private final Section mainSection;
+
+ // TODO manage within the CSS
+ private int labelColWidth = 150;
+ private int sectionSeparatorHeight = 10;
+ private int sectionBodyVIndent = 30;
+ private int sectionBodyHSpacing = 15;
+ private int sectionBodyVSpacing = 15;
+ private int rowLayoutHSpacing = 8;
+
+ // Context cached in the viewer
+ // The reference to translate from text to calendar and reverse
+ private DateFormat dateFormat = new SimpleDateFormat(
+ FormUtils.DEFAULT_SHORT_DATE_FORMAT);
+ private CmsImageManager imageManager;
+ private FileUploadListener fileUploadListener;
+
+ public FormPageViewer(Section mainSection, int style,
+ CmsEditable cmsEditable) throws RepositoryException {
+ super(mainSection, style, cmsEditable);
+ this.mainSection = mainSection;
+
+ if (getCmsEditable().canEdit()) {
+ fileUploadListener = new FUL();
+ }
+ }
+
+ @Override
+ protected void prepare(EditablePart part, Object caretPosition) {
+ if (part instanceof Img) {
+ ((Img) part).setFileUploadListener(fileUploadListener);
+ }
+ }
+
+ /** To be overridden.Save the edited part. */
+ protected void save(EditablePart part) throws RepositoryException {
+ Node node = null;
+ if (part instanceof EditableMultiStringProperty) {
+ EditableMultiStringProperty ept = (EditableMultiStringProperty) part;
+ // SWT : View
+ List<String> values = ept.getValues();
+ // JCR : Model
+ node = ept.getNode();
+ String propName = ept.getPropertyName();
+ if (values.isEmpty()) {
+ if (node.hasProperty(propName))
+ node.getProperty(propName).remove();
+ } else {
+ node.setProperty(propName, values.toArray(new String[0]));
+ }
+ // => Viewer : Controller
+ } else if (part instanceof EditablePropertyString) {
+ EditablePropertyString ept = (EditablePropertyString) part;
+ // SWT : View
+ String txt = ((Text) ept.getControl()).getText();
+ // JCR : Model
+ node = ept.getNode();
+ String propName = ept.getPropertyName();
+ if (FormUtils.notEmpty(txt)) {
+ if (node.hasProperty(propName))
+ node.getProperty(propName).remove();
+ } else {
+ setPropertySilently(node, propName, txt);
+ // node.setProperty(propName, txt);
+ }
+ // node.getSession().save();
+ // => Viewer : Controller
+ } else if (part instanceof EditablePropertyDate) {
+ EditablePropertyDate ept = (EditablePropertyDate) part;
+ Calendar cal = FormUtils.parseDate(dateFormat,
+ ((Text) ept.getControl()).getText());
+ node = ept.getNode();
+ String propName = ept.getPropertyName();
+ if (cal == null) {
+ if (node.hasProperty(propName))
+ node.getProperty(propName).remove();
+ } else {
+ node.setProperty(propName, cal);
+ }
+ // node.getSession().save();
+ // => Viewer : Controller
+ }
+ // TODO: make this configurable, sometimes we do not want to save the
+ // current session at this stage
+ if (node != null && node.getSession().hasPendingChanges()) {
+ JcrUtils.updateLastModified(node);
+ node.getSession().save();
+ }
+ }
+
+ @Override
+ protected void updateContent(EditablePart part) throws RepositoryException {
+ if (part instanceof EditableMultiStringProperty) {
+ EditableMultiStringProperty ept = (EditableMultiStringProperty) part;
+ // SWT : View
+ Node node = ept.getNode();
+ String propName = ept.getPropertyName();
+ List<String> valStrings = new ArrayList<String>();
+ if (node.hasProperty(propName)) {
+ Value[] values = node.getProperty(propName).getValues();
+ for (Value val : values)
+ valStrings.add(val.getString());
+ }
+ ept.setValues(valStrings);
+ } else if (part instanceof EditablePropertyString) {
+ // || part instanceof EditableLink
+ EditablePropertyString ept = (EditablePropertyString) part;
+ // JCR : Model
+ Node node = ept.getNode();
+ String propName = ept.getPropertyName();
+ if (node.hasProperty(propName)) {
+ String value = node.getProperty(propName).getString();
+ ept.setText(value);
+ } else
+ ept.setText("");
+ // => Viewer : Controller
+ } else if (part instanceof EditablePropertyDate) {
+ EditablePropertyDate ept = (EditablePropertyDate) part;
+ // JCR : Model
+ Node node = ept.getNode();
+ String propName = ept.getPropertyName();
+ if (node.hasProperty(propName))
+ ept.setText(dateFormat.format(node.getProperty(propName)
+ .getDate().getTime()));
+ else
+ ept.setText("");
+ } else if (part instanceof SectionPart) {
+ SectionPart sectionPart = (SectionPart) part;
+ Node partNode = sectionPart.getNode();
+ // use control AFTER setting style, since it may have been reset
+ if (part instanceof EditableImage) {
+ EditableImage editableImage = (EditableImage) part;
+ imageManager().load(partNode, part.getControl(),
+ editableImage.getPreferredImageSize());
+ }
+ }
+ }
+
+ // FILE UPLOAD LISTENER
+ protected class FUL implements FileUploadListener {
+
+ public FUL() {
+ }
+
+ public void uploadProgress(FileUploadEvent event) {
+ // TODO Monitor upload progress
+ }
+
+ public void uploadFailed(FileUploadEvent event) {
+ throw new CmsException("Upload failed " + event,
+ event.getException());
+ }
+
+ public void uploadFinished(FileUploadEvent event) {
+ for (FileDetails file : event.getFileDetails()) {
+ if (log.isDebugEnabled())
+ log.debug("Received: " + file.getFileName());
+ }
+ mainSection.getDisplay().syncExec(new Runnable() {
+ @Override
+ public void run() {
+ saveEdit();
+ }
+ });
+ FileUploadHandler uploadHandler = (FileUploadHandler) event
+ .getSource();
+ uploadHandler.dispose();
+ }
+ }
+
+ // FOCUS OUT LISTENER
+ protected FocusListener createFocusListener() {
+ return new FocusOutListener();
+ }
+
+ private class FocusOutListener implements FocusListener {
+ private static final long serialVersionUID = -6069205786732354186L;
+
+ @Override
+ public void focusLost(FocusEvent event) {
+ saveEdit();
+ }
+
+ @Override
+ public void focusGained(FocusEvent event) {
+ // does nothing;
+ }
+ }
+
+ // MOUSE LISTENER
+ @Override
+ protected MouseListener createMouseListener() {
+ return new ML();
+ }
+
+ private class ML extends MouseAdapter {
+ private static final long serialVersionUID = 8526890859876770905L;
+
+ @Override
+ public void mouseDoubleClick(MouseEvent e) {
+ if (e.button == 1) {
+ Control source = (Control) e.getSource();
+ if (getCmsEditable().canEdit()) {
+ if (getCmsEditable().isEditing()
+ && !(getEdited() instanceof Img)) {
+ if (source == mainSection)
+ return;
+ EditablePart part = findDataParent(source);
+ upload(part);
+ } else {
+ getCmsEditable().startEditing();
+ }
+ }
+ }
+ }
+
+ @Override
+ public void mouseDown(MouseEvent e) {
+ if (getCmsEditable().isEditing()) {
+ if (e.button == 1) {
+ Control source = (Control) e.getSource();
+ EditablePart composite = findDataParent(source);
+ Point point = new Point(e.x, e.y);
+ if (!(composite instanceof Img))
+ edit(composite, source.toDisplay(point));
+ } else if (e.button == 3) {
+ // EditablePart composite = findDataParent((Control) e
+ // .getSource());
+ // if (styledTools != null)
+ // styledTools.show(composite, new Point(e.x, e.y));
+ }
+ }
+ }
+
+ protected synchronized void upload(EditablePart part) {
+ if (part instanceof SectionPart) {
+ if (part instanceof Img) {
+ if (getEdited() == part)
+ return;
+ edit(part, null);
+ layout(part.getControl());
+ }
+ }
+ }
+ }
+
+ @Override
+ public Control getControl() {
+ return mainSection;
+ }
+
+ protected CmsImageManager imageManager() {
+ if (imageManager == null)
+ imageManager = CmsUtils.getCmsView().getImageManager();
+ return imageManager;
+ }
+
+ // LOCAL UI HELPERS
+ protected Section createSectionIfNeeded(Composite body, Node node)
+ throws RepositoryException {
+ Section section = null;
+ if (node != null) {
+ section = new Section(body, SWT.NO_FOCUS, node);
+ section.setLayoutData(CmsUtils.fillWidth());
+ section.setLayout(CmsUtils.noSpaceGridLayout());
+ }
+ return section;
+ }
+
+ protected void createSimpleLT(Composite bodyRow, Node node,
+ String propName, String label, String msg)
+ throws RepositoryException {
+ if (getCmsEditable().canEdit() || node.hasProperty(propName)) {
+ createPropertyLbl(bodyRow, label);
+ EditablePropertyString eps = new EditablePropertyString(bodyRow,
+ SWT.WRAP | SWT.LEFT, node, propName, msg);
+ eps.setMouseListener(getMouseListener());
+ eps.setFocusListener(getFocusListener());
+ eps.setLayoutData(CmsUtils.fillWidth());
+ }
+ }
+
+ protected void createMultiStringLT(Composite bodyRow, Node node,
+ String propName, String label, String msg)
+ throws RepositoryException {
+ boolean canEdit = getCmsEditable().canEdit();
+ if (canEdit || node.hasProperty(propName)) {
+ createPropertyLbl(bodyRow, label);
+
+ List<String> valueStrings = new ArrayList<String>();
+
+ if (node.hasProperty(propName)) {
+ Value[] values = node.getProperty(propName).getValues();
+ for (Value value : values)
+ valueStrings.add(value.getString());
+ }
+
+ // TODO use a drop down to display possible values to the end user
+ EditableMultiStringProperty emsp = new EditableMultiStringProperty(
+ bodyRow, SWT.SINGLE | SWT.LEAD, node, propName,
+ valueStrings, new String[] { "Implement this" }, msg,
+ canEdit ? getRemoveValueSelListener() : null);
+ addListeners(emsp);
+ // emsp.setMouseListener(getMouseListener());
+ emsp.setStyle(FormStyle.propertyMessage.style());
+ emsp.setLayoutData(CmsUtils.fillWidth());
+ }
+ }
+
+ protected Label createPropertyLbl(Composite parent, String value) {
+ return createPropertyLbl(parent, value, SWT.TOP);
+ }
+
+ protected Label createPropertyLbl(Composite parent, String value, int vAlign) {
+ Label label = new Label(parent, SWT.RIGHT | SWT.WRAP);
+ label.setText(value + " ");
+ CmsUtils.style(label, FormStyle.propertyLabel.style());
+ GridData gd = new GridData(SWT.RIGHT, vAlign, false, false);
+ gd.widthHint = labelColWidth;
+ label.setLayoutData(gd);
+ return label;
+ }
+
+ protected Label newStyledLabel(Composite parent, String style, String value) {
+ Label label = new Label(parent, SWT.NONE);
+ label.setText(value);
+ CmsUtils.style(label, style);
+ return label;
+ }
+
+ protected Composite createRowLayoutComposite(Composite parent)
+ throws RepositoryException {
+ Composite bodyRow = new Composite(parent, SWT.NO_FOCUS);
+ bodyRow.setLayoutData(CmsUtils.fillWidth());
+ RowLayout rl = new RowLayout(SWT.WRAP);
+ rl.type = SWT.HORIZONTAL;
+ rl.spacing = rowLayoutHSpacing;
+ rl.marginHeight = rl.marginWidth = 0;
+ rl.marginTop = rl.marginBottom = rl.marginLeft = rl.marginRight = 0;
+ bodyRow.setLayout(rl);
+ return bodyRow;
+ }
+
+ protected Composite createSectionBody(Composite parent, int nbOfCol) {
+ // The separator line. Ugly workaround that should be better managed via
+ // css
+ Composite header = new Composite(parent, SWT.NO_FOCUS);
+ CmsUtils.style(header, FormStyle.sectionHeader.style());
+ GridData gd = CmsUtils.fillWidth();
+ gd.verticalIndent = sectionSeparatorHeight;
+ gd.heightHint = 0;
+ header.setLayoutData(gd);
+
+ Composite bodyRow = new Composite(parent, SWT.NO_FOCUS);
+ gd = CmsUtils.fillWidth();
+ gd.verticalIndent = sectionBodyVIndent;
+ bodyRow.setLayoutData(gd);
+ GridLayout gl = new GridLayout(nbOfCol, false);
+ gl.horizontalSpacing = sectionBodyHSpacing;
+ gl.verticalSpacing = sectionBodyVSpacing;
+ bodyRow.setLayout(gl);
+ CmsUtils.style(bodyRow, FormStyle.section.style());
+
+ return bodyRow;
+ }
+
+ protected Composite createAddImgComposite(final Section section,
+ Composite parent, final Node parentNode) throws RepositoryException {
+
+ Composite body = new Composite(parent, SWT.NO_FOCUS);
+ body.setLayout(new GridLayout());
+
+ FormFileUploadReceiver receiver = new FormFileUploadReceiver(section,
+ parentNode, null);
+ final FileUploadHandler currentUploadHandler = new FileUploadHandler(
+ receiver);
+ if (fileUploadListener != null)
+ currentUploadHandler.addUploadListener(fileUploadListener);
+
+ // Button creation
+ final FileUpload fileUpload = new FileUpload(body, SWT.BORDER);
+ fileUpload.setText("Import an image");
+ fileUpload.setLayoutData(new GridData(SWT.CENTER, SWT.CENTER, true,
+ true));
+ fileUpload.addSelectionListener(new SelectionAdapter() {
+ private static final long serialVersionUID = 4869523412991968759L;
+
+ @Override
+ public void widgetSelected(SelectionEvent e) {
+ ServerPushSession pushSession = new ServerPushSession();
+ pushSession.start();
+ String uploadURL = currentUploadHandler.getUploadUrl();
+ fileUpload.submit(uploadURL);
+ }
+ });
+
+ return body;
+ }
+
+ protected class FormFileUploadReceiver extends FileUploadReceiver implements
+ CmsNames {
+
+ private Node context;
+ private Section section;
+ private String name;
+
+ public FormFileUploadReceiver(Section section, Node context, String name) {
+ this.context = context;
+ this.section = section;
+ this.name = name;
+ }
+
+ @Override
+ public void receive(InputStream stream, FileDetails details)
+ throws IOException {
+
+ if (name == null)
+ name = details.getFileName();
+ try {
+ imageManager().uploadImage(context, name, stream);
+ // TODO clean refresh strategy
+ section.getDisplay().asyncExec(new Runnable() {
+ @Override
+ public void run() {
+ try {
+ FormPageViewer.this.refresh(section);
+ section.layout();
+ section.getParent().layout();
+ } catch (RepositoryException re) {
+ throw new ArgeoException("unable to refresh "
+ + "image section for " + context);
+ }
+ }
+ });
+ } catch (RepositoryException re) {
+ throw new ArgeoException("unable to upload image " + name
+ + " at " + context);
+ }
+ }
+ }
+
+ protected void addListeners(StyledControl control) {
+ control.setMouseListener(getMouseListener());
+ control.setFocusListener(getFocusListener());
+ }
+
+ protected Img createImgComposite(Composite parent, Node node,
+ Point preferredSize) throws RepositoryException {
+ Img img = new Img(parent, SWT.NONE, node, preferredSize) {
+ private static final long serialVersionUID = 1297900641952417540L;
+
+ @Override
+ protected void setContainerLayoutData(Composite composite) {
+ composite.setLayoutData(CmsUtils.grabWidth(SWT.CENTER,
+ SWT.DEFAULT));
+ }
+
+ @Override
+ protected void setControlLayoutData(Control control) {
+ control.setLayoutData(CmsUtils.grabWidth(SWT.CENTER,
+ SWT.DEFAULT));
+ }
+ };
+ img.setLayoutData(CmsUtils.grabWidth(SWT.CENTER, SWT.DEFAULT));
+ updateContent(img);
+ addListeners(img);
+ return img;
+ }
+
+ protected Composite addDeleteAbility(final Section section,
+ final Node sessionNode, int topWeight, int rightWeight) {
+ Composite comp = new Composite(section, SWT.NONE);
+ comp.setLayoutData(CmsUtils.fillAll());
+ comp.setLayout(new FormLayout());
+
+ // The body to be populated
+ Composite body = new Composite(comp, SWT.NO_FOCUS);
+ body.setLayoutData(EclipseUiUtils.fillFormData());
+
+ if (getCmsEditable().canEdit()) {
+ // the delete button
+ Button deleteBtn = new Button(comp, SWT.FLAT);
+ CmsUtils.style(deleteBtn, FormStyle.deleteOverlay.style());
+ FormData formData = new FormData();
+ formData.right = new FormAttachment(rightWeight, 0);
+ formData.top = new FormAttachment(topWeight, 0);
+ deleteBtn.setLayoutData(formData);
+ deleteBtn.moveAbove(body);
+
+ deleteBtn.addSelectionListener(new SelectionAdapter() {
+ private static final long serialVersionUID = 4304223543657238462L;
+
+ @Override
+ public void widgetSelected(SelectionEvent e) {
+ super.widgetSelected(e);
+ if (MessageDialog.openConfirm(section.getShell(),
+ "Confirm deletion",
+ "Are you really you want to remove this?")) {
+ Session session;
+ try {
+ session = sessionNode.getSession();
+ Section parSection = section.getParentSection();
+ sessionNode.remove();
+ session.save();
+ refresh(parSection);
+ layout(parSection);
+ } catch (RepositoryException re) {
+ throw new ArgeoException("Unable to delete "
+ + sessionNode, re);
+ }
+
+ }
+
+ }
+ });
+ }
+ return body;
+ }
+
+ // LOCAL HELPERS FOR NODE MANAGEMENT
+ protected Node getOrCreateNode(Node parent, String nodeType, String nodeName)
+ throws RepositoryException {
+ Node node = null;
+ if (getCmsEditable().canEdit() && !parent.hasNode(nodeName)) {
+ node = JcrUtils.mkdirs(parent, nodeName, nodeType);
+ parent.getSession().save();
+ }
+
+ if (getCmsEditable().canEdit() || parent.hasNode(nodeName))
+ node = parent.getNode(nodeName);
+
+ return node;
+ }
+
+ private SelectionListener getRemoveValueSelListener() {
+ return new SelectionAdapter() {
+ private static final long serialVersionUID = 9022259089907445195L;
+
+ @Override
+ public void widgetSelected(SelectionEvent e) {
+ Object source = e.getSource();
+ if (source instanceof Button) {
+ Button btn = (Button) source;
+ Object obj = btn.getData(FormConstants.LINKED_VALUE);
+ EditablePart ep = findDataParent(btn);
+ if (ep != null && ep instanceof EditableMultiStringProperty) {
+ EditableMultiStringProperty emsp = (EditableMultiStringProperty) ep;
+ List<String> values = emsp.getValues();
+ if (values.contains(obj)) {
+ values.remove(values.indexOf(obj));
+ emsp.setValues(values);
+ try {
+ save(emsp);
+ // TODO workaround to force refresh
+ edit(emsp, 0);
+ cancelEdit();
+ } catch (RepositoryException e1) {
+ throw new ArgeoException(
+ "Unable to remove value " + obj, e1);
+ }
+ layout(emsp);
+ }
+ }
+ }
+ }
+ };
+ }
+
+ protected void setPropertySilently(Node node, String propName, String value)
+ throws RepositoryException {
+ try {
+ // TODO Clean this:
+ // Format strings to replace \n
+ value = value.replaceAll("\n", "<br/>");
+ // Do not make the update if validation fails
+ try {
+ MarkupValidatorCopy.getInstance().validate(value);
+ } catch (Exception e) {
+ log.warn("Cannot set [" + value + "] on prop " + propName
+ + "of " + node + ", String cannot be validated - "
+ + e.getMessage());
+ return;
+ }
+ // TODO check if the newly created property is of the correct type,
+ // otherwise the property will be silently created with a STRING
+ // property type.
+ node.setProperty(propName, value);
+ } catch (ValueFormatException vfe) {
+ log.warn("Cannot set [" + value + "] on prop " + propName + "of "
+ + node + " - " + vfe.getMessage());
+ }
+ }
+}
\ No newline at end of file
--- /dev/null
+package org.argeo.cms.forms;
+
+/** Syles used */
+public enum FormStyle {
+ // Main
+ form, title,
+ // main part
+ header, headerBtn, headerCombo, section, sectionHeader,
+ // Property fields
+ propertyLabel, propertyText, propertyMessage,
+ // Date
+ popupCalendar,
+ // Buttons
+ starOverlay, deleteOverlay, updateOverlay, deleteOverlaySmall, calendar, delete,
+ // Contacts
+ email, address, phone, website,
+ // Social Media
+ facebook, twitter, linkedIn, instagram;
+
+ public String style() {
+ return form.name() + '_' + name();
+ }
+
+ // TODO clean button style management
+ public final static String BUTTON_SUFFIX = "_btn";
+}
--- /dev/null
+package org.argeo.cms.forms;
+
+import java.text.DateFormat;
+import java.text.ParseException;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.GregorianCalendar;
+
+import javax.jcr.Node;
+import javax.jcr.RepositoryException;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.argeo.ArgeoException;
+import org.argeo.cms.CmsView;
+import org.argeo.cms.util.CmsUtils;
+import org.eclipse.jface.fieldassist.ControlDecoration;
+import org.eclipse.jface.fieldassist.FieldDecorationRegistry;
+import org.eclipse.jface.viewers.DoubleClickEvent;
+import org.eclipse.jface.viewers.IDoubleClickListener;
+import org.eclipse.jface.viewers.IStructuredSelection;
+import org.eclipse.jface.viewers.TableViewer;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.graphics.Color;
+import org.eclipse.swt.graphics.Image;
+import org.eclipse.swt.widgets.Label;
+import org.eclipse.swt.widgets.Text;
+
+/** Utilitary methods to ease implementation of CMS forms */
+public class FormUtils {
+ private final static Log log = LogFactory.getLog(FormUtils.class);
+
+ public final static String DEFAULT_SHORT_DATE_FORMAT = "dd/MM/yyyy";
+
+ /** Simply checks if a string is not null nor empty */
+ public static boolean notEmpty(String stringToTest) {
+ return stringToTest == null || "".equals(stringToTest.trim());
+ }
+
+ /** Best effort to convert a String to a calendar. Fails silently */
+ public static Calendar parseDate(DateFormat dateFormat, String calStr) {
+ Calendar cal = null;
+ if (notEmpty(calStr)) {
+ try {
+ Date date = dateFormat.parse(calStr);
+ cal = new GregorianCalendar();
+ cal.setTime(date);
+ } catch (ParseException pe) {
+ // Silent
+ log.warn("Unable to parse date: " + calStr + " - msg: "
+ + pe.getMessage());
+ }
+ }
+ return cal;
+ }
+
+ /** Add a double click listener on tables that display a JCR node list */
+ public static void addCanonicalDoubleClickListener(final TableViewer v) {
+ v.addDoubleClickListener(new IDoubleClickListener() {
+
+ @Override
+ public void doubleClick(DoubleClickEvent event) {
+ CmsView cmsView = CmsUtils.getCmsView();
+ Node node = (Node) ((IStructuredSelection) event.getSelection())
+ .getFirstElement();
+ try {
+ cmsView.navigateTo(node.getPath());
+ } catch (RepositoryException e) {
+ throw new ArgeoException("Unable to get path for node "
+ + node + " before calling navigateTo(path)", e);
+ }
+ }
+ });
+ }
+
+ // MANAGE ERROR DECORATION
+
+ public static ControlDecoration addDecoration(final Text text) {
+ final ControlDecoration dynDecoration = new ControlDecoration(text,
+ SWT.LEFT);
+ Image icon = getDecorationImage(FieldDecorationRegistry.DEC_ERROR);
+ dynDecoration.setImage(icon);
+ dynDecoration.setMarginWidth(3);
+ dynDecoration.hide();
+ return dynDecoration;
+ }
+
+ public static void refreshDecoration(Text text, ControlDecoration deco,
+ boolean isValid, boolean clean) {
+ if (isValid || clean) {
+ text.setBackground(null);
+ deco.hide();
+ } else {
+ text.setBackground(new Color(text.getDisplay(), 250, 200, 150));
+ deco.show();
+ }
+ }
+
+ public static Image getDecorationImage(String image) {
+ FieldDecorationRegistry registry = FieldDecorationRegistry.getDefault();
+ return registry.getFieldDecoration(image).getImage();
+ }
+
+ public static void addCompulsoryDecoration(Label label) {
+ final ControlDecoration dynDecoration = new ControlDecoration(label,
+ SWT.RIGHT | SWT.TOP);
+ Image icon = getDecorationImage(FieldDecorationRegistry.DEC_REQUIRED);
+ dynDecoration.setImage(icon);
+ dynDecoration.setMarginWidth(3);
+ }
+
+ // TODO the read only generation of read only links for various contact type
+ // should be factorised in the cms Utils.
+ /**
+ * Creates the read-only HTML snippet to display in a label with styling
+ * enabled in order to provide a click-able phone number
+ */
+ public static String getPhoneLink(String value) {
+ return getPhoneLink(value, value);
+ }
+
+ /**
+ * Creates the read-only HTML snippet to display in a label with styling
+ * enabled in order to provide a click-able phone number
+ *
+ * @param value
+ * @param label
+ * a potentially distinct label
+ * @return
+ */
+ public static String getPhoneLink(String value, String label) {
+ StringBuilder builder = new StringBuilder();
+ builder.append("<a href=\"tel:");
+ builder.append(value).append("\" target=\"_blank\" >").append(label)
+ .append("</a>");
+ return builder.toString();
+ }
+
+ /**
+ * Creates the read-only HTML snippet to display in a label with styling
+ * enabled in order to provide a click-able mail
+ */
+ public static String getMailLink(String value) {
+ return getMailLink(value, value);
+ }
+
+ /**
+ * Creates the read-only HTML snippet to display in a label with styling
+ * enabled in order to provide a click-able mail
+ *
+ * @param value
+ * @param label
+ * a potentially distinct label
+ * @return
+ */
+ public static String getMailLink(String value, String label) {
+ StringBuilder builder = new StringBuilder();
+ value = replaceAmpersand(value);
+ builder.append("<a href=\"mailto:");
+ builder.append(value).append("\" >").append(label).append("</a>");
+ return builder.toString();
+ }
+
+ /**
+ * Creates the read-only HTML snippet to display in a label with styling
+ * enabled in order to provide a click-able link
+ */
+ public static String getUrlLink(String value) {
+ return getUrlLink(value, value);
+ }
+
+ /**
+ * Creates the read-only HTML snippet to display in a label with styling
+ * enabled in order to provide a click-able link
+ */
+ public static String getUrlLink(String value, String label) {
+ StringBuilder builder = new StringBuilder();
+ value = replaceAmpersand(value);
+ label = replaceAmpersand(label);
+ if (!(value.startsWith("http://") || value.startsWith("https://")))
+ value = "http://" + value;
+ builder.append("<a href=\"");
+ builder.append(value + "\" target=\"_blank\" >" + label + "</a>");
+ return builder.toString();
+ }
+
+ private static String AMPERSAND = "&";
+
+ /**
+ * Cleans a String by replacing any '&' by its HTML encoding '&' to
+ * avoid <code>SAXParseException</code> while rendering HTML with RWT
+ */
+ public static String replaceAmpersand(String value) {
+ value = value.replaceAll("&(?![#a-zA-Z0-9]+;)", AMPERSAND);
+ return value;
+ }
+
+ // Prevents instantiation
+ private FormUtils() {
+ }
+}
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;
+import org.argeo.cms.forms.FormPageViewer;
import org.eclipse.rap.rwt.SingletonUtil;
import org.eclipse.swt.widgets.Widget;
import org.xml.sax.Attributes;
import org.xml.sax.InputSource;
import org.xml.sax.helpers.DefaultHandler;
-/** Copy of RAP v2.3 since it is in an internal package. */
-class MarkupValidatorCopy {
-
- // Used by Eclipse Scout project
- public static final String MARKUP_VALIDATION_DISABLED
- = "org.eclipse.rap.rwt.markupValidationDisabled";
-
- private static final String DTD = createDTD();
- private static final Map<String, String[]> SUPPORTED_ELEMENTS = createSupportedElementsMap();
- private final SAXParser saxParser;
-
- public static MarkupValidatorCopy getInstance() {
- return SingletonUtil.getSessionInstance( MarkupValidatorCopy.class );
- }
-
- public MarkupValidatorCopy() {
- saxParser = createSAXParser();
- }
-
- public void validate( String text ) {
- StringBuilder markup = new StringBuilder();
- markup.append( DTD );
- markup.append( "<html>" );
- markup.append( text );
- markup.append( "</html>" );
- InputSource inputSource = new InputSource( new StringReader( markup.toString() ) );
- try {
- saxParser.parse( inputSource, new MarkupHandler() );
- } catch( RuntimeException exception ) {
- throw exception;
- } catch( Exception exception ) {
- throw new IllegalArgumentException( "Failed to parse markup text", exception );
- }
- }
-
- public static boolean isValidationDisabledFor( Widget widget ) {
- return Boolean.TRUE.equals( widget.getData( MARKUP_VALIDATION_DISABLED ) );
- }
-
- private static SAXParser createSAXParser() {
- SAXParser result = null;
- SAXParserFactory parserFactory = SAXParserFactory.newInstance();
- try {
- result = parserFactory.newSAXParser();
- } catch( Exception exception ) {
- throw new RuntimeException( "Failed to create SAX parser", exception );
- }
- return result;
- }
-
- private static String createDTD() {
- StringBuilder result = new StringBuilder();
- result.append( "<!DOCTYPE html [" );
- result.append( "<!ENTITY quot \""\">" );
- result.append( "<!ENTITY amp \"&\">" );
- result.append( "<!ENTITY apos \"'\">" );
- result.append( "<!ENTITY lt \"<\">" );
- result.append( "<!ENTITY gt \">\">" );
- result.append( "<!ENTITY nbsp \" \">" );
- result.append( "<!ENTITY ensp \" \">" );
- result.append( "<!ENTITY emsp \" \">" );
- result.append( "<!ENTITY ndash \"–\">" );
- result.append( "<!ENTITY mdash \"—\">" );
- result.append( "]>" );
- return result.toString();
- }
-
- private static Map<String, String[]> createSupportedElementsMap() {
- Map<String, String[]> result = new HashMap<String, String[]>();
- result.put( "html", new String[ 0 ] );
- result.put( "br", new String[ 0 ] );
- result.put( "b", new String[] { "style" } );
- result.put( "strong", new String[] { "style" } );
- result.put( "i", new String[] { "style" } );
- result.put( "em", new String[] { "style" } );
- result.put( "sub", new String[] { "style" } );
- result.put( "sup", new String[] { "style" } );
- result.put( "big", new String[] { "style" } );
- result.put( "small", new String[] { "style" } );
- result.put( "del", new String[] { "style" } );
- result.put( "ins", new String[] { "style" } );
- result.put( "code", new String[] { "style" } );
- result.put( "samp", new String[] { "style" } );
- result.put( "kbd", new String[] { "style" } );
- result.put( "var", new String[] { "style" } );
- result.put( "cite", new String[] { "style" } );
- result.put( "dfn", new String[] { "style" } );
- result.put( "q", new String[] { "style" } );
- result.put( "abbr", new String[] { "style", "title" } );
- result.put( "span", new String[] { "style" } );
- result.put( "img", new String[] { "style", "src", "width", "height", "title", "alt" } );
- result.put( "a", new String[] { "style", "href", "target", "title" } );
- return result;
- }
-
- private static class MarkupHandler extends DefaultHandler {
-
- @Override
- public void startElement( String uri, String localName, String name, Attributes attributes ) {
- checkSupportedElements( name, attributes );
- checkSupportedAttributes( name, attributes );
- checkMandatoryAttributes( name, attributes );
- }
-
- private static void checkSupportedElements( String elementName, Attributes attributes ) {
- if( !SUPPORTED_ELEMENTS.containsKey( elementName ) ) {
- throw new IllegalArgumentException( "Unsupported element in markup text: " + elementName );
- }
- }
-
- private static void checkSupportedAttributes( String elementName, Attributes attributes ) {
- if( attributes.getLength() > 0 ) {
- List<String> supportedAttributes = Arrays.asList( SUPPORTED_ELEMENTS.get( elementName ) );
- int index = 0;
- String attributeName = attributes.getQName( index );
- while( attributeName != null ) {
- if( !supportedAttributes.contains( attributeName ) ) {
- String message = "Unsupported attribute \"{0}\" for element \"{1}\" in markup text";
- message = MessageFormat.format( message, new Object[] { attributeName, elementName } );
- throw new IllegalArgumentException( message );
- }
- index++;
- attributeName = attributes.getQName( index );
- }
- }
- }
-
- private static void checkMandatoryAttributes( String elementName, Attributes attributes ) {
- checkIntAttribute( elementName, attributes, "img", "width" );
- checkIntAttribute( elementName, attributes, "img", "height" );
- }
-
- private static void checkIntAttribute( String elementName,
- Attributes attributes,
- String checkedElementName,
- String checkedAttributeName )
- {
- if( checkedElementName.equals( elementName ) ) {
- String attribute = attributes.getValue( checkedAttributeName );
- try {
- Integer.parseInt( attribute );
- } catch( NumberFormatException exception ) {
- String message
- = "Mandatory attribute \"{0}\" for element \"{1}\" is missing or not a valid integer";
- Object[] arguments = new Object[] { checkedAttributeName, checkedElementName };
- message = MessageFormat.format( message, arguments );
- throw new IllegalArgumentException( message );
- }
- }
- }
-
- }
+/**
+ * Copy of RAP v2.3 since it is in an internal package.
+ *
+ * FIXME made public to enable validation from the {@link FormPageViewer}
+ */
+public class MarkupValidatorCopy {
+
+ // Used by Eclipse Scout project
+ public static final String MARKUP_VALIDATION_DISABLED = "org.eclipse.rap.rwt.markupValidationDisabled";
+
+ private static final String DTD = createDTD();
+ private static final Map<String, String[]> SUPPORTED_ELEMENTS = createSupportedElementsMap();
+ private final SAXParser saxParser;
+
+ public static MarkupValidatorCopy getInstance() {
+ return SingletonUtil.getSessionInstance(MarkupValidatorCopy.class);
+ }
+
+ public MarkupValidatorCopy() {
+ saxParser = createSAXParser();
+ }
+
+ public void validate(String text) {
+ StringBuilder markup = new StringBuilder();
+ markup.append(DTD);
+ markup.append("<html>");
+ markup.append(text);
+ markup.append("</html>");
+ InputSource inputSource = new InputSource(new StringReader(
+ markup.toString()));
+ try {
+ saxParser.parse(inputSource, new MarkupHandler());
+ } catch (RuntimeException exception) {
+ throw exception;
+ } catch (Exception exception) {
+ throw new IllegalArgumentException("Failed to parse markup text",
+ exception);
+ }
+ }
+
+ public static boolean isValidationDisabledFor(Widget widget) {
+ return Boolean.TRUE.equals(widget.getData(MARKUP_VALIDATION_DISABLED));
+ }
+
+ private static SAXParser createSAXParser() {
+ SAXParser result = null;
+ SAXParserFactory parserFactory = SAXParserFactory.newInstance();
+ try {
+ result = parserFactory.newSAXParser();
+ } catch (Exception exception) {
+ throw new RuntimeException("Failed to create SAX parser", exception);
+ }
+ return result;
+ }
+
+ private static String createDTD() {
+ StringBuilder result = new StringBuilder();
+ result.append("<!DOCTYPE html [");
+ result.append("<!ENTITY quot \""\">");
+ result.append("<!ENTITY amp \"&\">");
+ result.append("<!ENTITY apos \"'\">");
+ result.append("<!ENTITY lt \"<\">");
+ result.append("<!ENTITY gt \">\">");
+ result.append("<!ENTITY nbsp \" \">");
+ result.append("<!ENTITY ensp \" \">");
+ result.append("<!ENTITY emsp \" \">");
+ result.append("<!ENTITY ndash \"–\">");
+ result.append("<!ENTITY mdash \"—\">");
+ result.append("]>");
+ return result.toString();
+ }
+
+ private static Map<String, String[]> createSupportedElementsMap() {
+ Map<String, String[]> result = new HashMap<String, String[]>();
+ result.put("html", new String[0]);
+ result.put("br", new String[0]);
+ result.put("b", new String[] { "style" });
+ result.put("strong", new String[] { "style" });
+ result.put("i", new String[] { "style" });
+ result.put("em", new String[] { "style" });
+ result.put("sub", new String[] { "style" });
+ result.put("sup", new String[] { "style" });
+ result.put("big", new String[] { "style" });
+ result.put("small", new String[] { "style" });
+ result.put("del", new String[] { "style" });
+ result.put("ins", new String[] { "style" });
+ result.put("code", new String[] { "style" });
+ result.put("samp", new String[] { "style" });
+ result.put("kbd", new String[] { "style" });
+ result.put("var", new String[] { "style" });
+ result.put("cite", new String[] { "style" });
+ result.put("dfn", new String[] { "style" });
+ result.put("q", new String[] { "style" });
+ result.put("abbr", new String[] { "style", "title" });
+ result.put("span", new String[] { "style" });
+ result.put("img", new String[] { "style", "src", "width", "height",
+ "title", "alt" });
+ result.put("a", new String[] { "style", "href", "target", "title" });
+ return result;
+ }
+
+ private static class MarkupHandler extends DefaultHandler {
+
+ @Override
+ public void startElement(String uri, String localName, String name,
+ Attributes attributes) {
+ checkSupportedElements(name, attributes);
+ checkSupportedAttributes(name, attributes);
+ checkMandatoryAttributes(name, attributes);
+ }
+
+ private static void checkSupportedElements(String elementName,
+ Attributes attributes) {
+ if (!SUPPORTED_ELEMENTS.containsKey(elementName)) {
+ throw new IllegalArgumentException(
+ "Unsupported element in markup text: " + elementName);
+ }
+ }
+
+ private static void checkSupportedAttributes(String elementName,
+ Attributes attributes) {
+ if (attributes.getLength() > 0) {
+ List<String> supportedAttributes = Arrays
+ .asList(SUPPORTED_ELEMENTS.get(elementName));
+ int index = 0;
+ String attributeName = attributes.getQName(index);
+ while (attributeName != null) {
+ if (!supportedAttributes.contains(attributeName)) {
+ String message = "Unsupported attribute \"{0}\" for element \"{1}\" in markup text";
+ message = MessageFormat.format(message, new Object[] {
+ attributeName, elementName });
+ throw new IllegalArgumentException(message);
+ }
+ index++;
+ attributeName = attributes.getQName(index);
+ }
+ }
+ }
+
+ private static void checkMandatoryAttributes(String elementName,
+ Attributes attributes) {
+ checkIntAttribute(elementName, attributes, "img", "width");
+ checkIntAttribute(elementName, attributes, "img", "height");
+ }
+
+ private static void checkIntAttribute(String elementName,
+ Attributes attributes, String checkedElementName,
+ String checkedAttributeName) {
+ if (checkedElementName.equals(elementName)) {
+ String attribute = attributes.getValue(checkedAttributeName);
+ try {
+ Integer.parseInt(attribute);
+ } catch (NumberFormatException exception) {
+ String message = "Mandatory attribute \"{0}\" for element \"{1}\" is missing or not a valid integer";
+ Object[] arguments = new Object[] { checkedAttributeName,
+ checkedElementName };
+ message = MessageFormat.format(message, arguments);
+ throw new IllegalArgumentException(message);
+ }
+ }
+ }
+
+ }
}