1 package org
.argeo
.cms
.forms
;
3 import java
.io
.IOException
;
4 import java
.io
.InputStream
;
5 import java
.text
.DateFormat
;
6 import java
.text
.SimpleDateFormat
;
7 import java
.util
.ArrayList
;
8 import java
.util
.Calendar
;
11 import javax
.jcr
.Node
;
12 import javax
.jcr
.RepositoryException
;
13 import javax
.jcr
.Session
;
14 import javax
.jcr
.Value
;
15 import javax
.jcr
.ValueFormatException
;
17 import org
.apache
.commons
.logging
.Log
;
18 import org
.apache
.commons
.logging
.LogFactory
;
19 import org
.argeo
.ArgeoException
;
20 import org
.argeo
.cms
.CmsEditable
;
21 import org
.argeo
.cms
.CmsException
;
22 import org
.argeo
.cms
.CmsImageManager
;
23 import org
.argeo
.cms
.CmsNames
;
24 import org
.argeo
.cms
.internal
.text
.MarkupValidatorCopy
;
25 import org
.argeo
.cms
.text
.Img
;
26 import org
.argeo
.cms
.util
.CmsUtils
;
27 import org
.argeo
.cms
.viewers
.AbstractPageViewer
;
28 import org
.argeo
.cms
.viewers
.EditablePart
;
29 import org
.argeo
.cms
.viewers
.Section
;
30 import org
.argeo
.cms
.viewers
.SectionPart
;
31 import org
.argeo
.cms
.widgets
.EditableImage
;
32 import org
.argeo
.cms
.widgets
.StyledControl
;
33 import org
.argeo
.eclipse
.ui
.EclipseUiUtils
;
34 import org
.argeo
.jcr
.JcrUtils
;
35 import org
.eclipse
.jface
.dialogs
.MessageDialog
;
36 import org
.eclipse
.rap
.addons
.fileupload
.FileDetails
;
37 import org
.eclipse
.rap
.addons
.fileupload
.FileUploadEvent
;
38 import org
.eclipse
.rap
.addons
.fileupload
.FileUploadHandler
;
39 import org
.eclipse
.rap
.addons
.fileupload
.FileUploadListener
;
40 import org
.eclipse
.rap
.addons
.fileupload
.FileUploadReceiver
;
41 import org
.eclipse
.rap
.rwt
.service
.ServerPushSession
;
42 import org
.eclipse
.rap
.rwt
.widgets
.FileUpload
;
43 import org
.eclipse
.swt
.SWT
;
44 import org
.eclipse
.swt
.events
.FocusEvent
;
45 import org
.eclipse
.swt
.events
.FocusListener
;
46 import org
.eclipse
.swt
.events
.MouseAdapter
;
47 import org
.eclipse
.swt
.events
.MouseEvent
;
48 import org
.eclipse
.swt
.events
.MouseListener
;
49 import org
.eclipse
.swt
.events
.SelectionAdapter
;
50 import org
.eclipse
.swt
.events
.SelectionEvent
;
51 import org
.eclipse
.swt
.events
.SelectionListener
;
52 import org
.eclipse
.swt
.graphics
.Point
;
53 import org
.eclipse
.swt
.layout
.FormAttachment
;
54 import org
.eclipse
.swt
.layout
.FormData
;
55 import org
.eclipse
.swt
.layout
.FormLayout
;
56 import org
.eclipse
.swt
.layout
.GridData
;
57 import org
.eclipse
.swt
.layout
.GridLayout
;
58 import org
.eclipse
.swt
.layout
.RowLayout
;
59 import org
.eclipse
.swt
.widgets
.Button
;
60 import org
.eclipse
.swt
.widgets
.Composite
;
61 import org
.eclipse
.swt
.widgets
.Control
;
62 import org
.eclipse
.swt
.widgets
.Label
;
63 import org
.eclipse
.swt
.widgets
.Text
;
65 /** Manage life cycle of a form page that is linked to a given node */
66 public class FormPageViewer
extends AbstractPageViewer
{
67 private final static Log log
= LogFactory
.getLog(FormPageViewer
.class);
68 private static final long serialVersionUID
= 5277789504209413500L;
70 private final Section mainSection
;
72 // TODO manage within the CSS
73 private int labelColWidth
= 150;
74 private int sectionSeparatorHeight
= 10;
75 private int sectionBodyVIndent
= 30;
76 private int sectionBodyHSpacing
= 15;
77 private int sectionBodyVSpacing
= 15;
78 private int rowLayoutHSpacing
= 8;
80 // Context cached in the viewer
81 // The reference to translate from text to calendar and reverse
82 private DateFormat dateFormat
= new SimpleDateFormat(
83 FormUtils
.DEFAULT_SHORT_DATE_FORMAT
);
84 private CmsImageManager imageManager
;
85 private FileUploadListener fileUploadListener
;
87 public FormPageViewer(Section mainSection
, int style
,
88 CmsEditable cmsEditable
) throws RepositoryException
{
89 super(mainSection
, style
, cmsEditable
);
90 this.mainSection
= mainSection
;
92 if (getCmsEditable().canEdit()) {
93 fileUploadListener
= new FUL();
98 protected void prepare(EditablePart part
, Object caretPosition
) {
99 if (part
instanceof Img
) {
100 ((Img
) part
).setFileUploadListener(fileUploadListener
);
104 /** To be overridden.Save the edited part. */
105 protected void save(EditablePart part
) throws RepositoryException
{
107 if (part
instanceof EditableMultiStringProperty
) {
108 EditableMultiStringProperty ept
= (EditableMultiStringProperty
) part
;
110 List
<String
> values
= ept
.getValues();
112 node
= ept
.getNode();
113 String propName
= ept
.getPropertyName();
114 if (values
.isEmpty()) {
115 if (node
.hasProperty(propName
))
116 node
.getProperty(propName
).remove();
118 node
.setProperty(propName
, values
.toArray(new String
[0]));
120 // => Viewer : Controller
121 } else if (part
instanceof EditablePropertyString
) {
122 EditablePropertyString ept
= (EditablePropertyString
) part
;
124 String txt
= ((Text
) ept
.getControl()).getText();
126 node
= ept
.getNode();
127 String propName
= ept
.getPropertyName();
128 if (FormUtils
.notEmpty(txt
)) {
129 if (node
.hasProperty(propName
))
130 node
.getProperty(propName
).remove();
132 setPropertySilently(node
, propName
, txt
);
133 // node.setProperty(propName, txt);
135 // node.getSession().save();
136 // => Viewer : Controller
137 } else if (part
instanceof EditablePropertyDate
) {
138 EditablePropertyDate ept
= (EditablePropertyDate
) part
;
139 Calendar cal
= FormUtils
.parseDate(dateFormat
,
140 ((Text
) ept
.getControl()).getText());
141 node
= ept
.getNode();
142 String propName
= ept
.getPropertyName();
144 if (node
.hasProperty(propName
))
145 node
.getProperty(propName
).remove();
147 node
.setProperty(propName
, cal
);
149 // node.getSession().save();
150 // => Viewer : Controller
152 // TODO: make this configurable, sometimes we do not want to save the
153 // current session at this stage
154 if (node
!= null && node
.getSession().hasPendingChanges()) {
155 JcrUtils
.updateLastModified(node
);
156 node
.getSession().save();
161 protected void updateContent(EditablePart part
) throws RepositoryException
{
162 if (part
instanceof EditableMultiStringProperty
) {
163 EditableMultiStringProperty ept
= (EditableMultiStringProperty
) part
;
165 Node node
= ept
.getNode();
166 String propName
= ept
.getPropertyName();
167 List
<String
> valStrings
= new ArrayList
<String
>();
168 if (node
.hasProperty(propName
)) {
169 Value
[] values
= node
.getProperty(propName
).getValues();
170 for (Value val
: values
)
171 valStrings
.add(val
.getString());
173 ept
.setValues(valStrings
);
174 } else if (part
instanceof EditablePropertyString
) {
175 // || part instanceof EditableLink
176 EditablePropertyString ept
= (EditablePropertyString
) part
;
178 Node node
= ept
.getNode();
179 String propName
= ept
.getPropertyName();
180 if (node
.hasProperty(propName
)) {
181 String value
= node
.getProperty(propName
).getString();
185 // => Viewer : Controller
186 } else if (part
instanceof EditablePropertyDate
) {
187 EditablePropertyDate ept
= (EditablePropertyDate
) part
;
189 Node node
= ept
.getNode();
190 String propName
= ept
.getPropertyName();
191 if (node
.hasProperty(propName
))
192 ept
.setText(dateFormat
.format(node
.getProperty(propName
)
193 .getDate().getTime()));
196 } else if (part
instanceof SectionPart
) {
197 SectionPart sectionPart
= (SectionPart
) part
;
198 Node partNode
= sectionPart
.getNode();
199 // use control AFTER setting style, since it may have been reset
200 if (part
instanceof EditableImage
) {
201 EditableImage editableImage
= (EditableImage
) part
;
202 imageManager().load(partNode
, part
.getControl(),
203 editableImage
.getPreferredImageSize());
208 // FILE UPLOAD LISTENER
209 protected class FUL
implements FileUploadListener
{
214 public void uploadProgress(FileUploadEvent event
) {
215 // TODO Monitor upload progress
218 public void uploadFailed(FileUploadEvent event
) {
219 throw new CmsException("Upload failed " + event
,
220 event
.getException());
223 public void uploadFinished(FileUploadEvent event
) {
224 for (FileDetails file
: event
.getFileDetails()) {
225 if (log
.isDebugEnabled())
226 log
.debug("Received: " + file
.getFileName());
228 mainSection
.getDisplay().syncExec(new Runnable() {
234 FileUploadHandler uploadHandler
= (FileUploadHandler
) event
236 uploadHandler
.dispose();
240 // FOCUS OUT LISTENER
241 protected FocusListener
createFocusListener() {
242 return new FocusOutListener();
245 private class FocusOutListener
implements FocusListener
{
246 private static final long serialVersionUID
= -6069205786732354186L;
249 public void focusLost(FocusEvent event
) {
254 public void focusGained(FocusEvent event
) {
261 protected MouseListener
createMouseListener() {
265 private class ML
extends MouseAdapter
{
266 private static final long serialVersionUID
= 8526890859876770905L;
269 public void mouseDoubleClick(MouseEvent e
) {
271 Control source
= (Control
) e
.getSource();
272 if (getCmsEditable().canEdit()) {
273 if (getCmsEditable().isEditing()
274 && !(getEdited() instanceof Img
)) {
275 if (source
== mainSection
)
277 EditablePart part
= findDataParent(source
);
280 getCmsEditable().startEditing();
287 public void mouseDown(MouseEvent e
) {
288 if (getCmsEditable().isEditing()) {
290 Control source
= (Control
) e
.getSource();
291 EditablePart composite
= findDataParent(source
);
292 Point point
= new Point(e
.x
, e
.y
);
293 if (!(composite
instanceof Img
))
294 edit(composite
, source
.toDisplay(point
));
295 } else if (e
.button
== 3) {
296 // EditablePart composite = findDataParent((Control) e
298 // if (styledTools != null)
299 // styledTools.show(composite, new Point(e.x, e.y));
304 protected synchronized void upload(EditablePart part
) {
305 if (part
instanceof SectionPart
) {
306 if (part
instanceof Img
) {
307 if (getEdited() == part
)
310 layout(part
.getControl());
317 public Control
getControl() {
321 protected CmsImageManager
imageManager() {
322 if (imageManager
== null)
323 imageManager
= CmsUtils
.getCmsView().getImageManager();
328 protected Section
createSectionIfNeeded(Composite body
, Node node
)
329 throws RepositoryException
{
330 Section section
= null;
332 section
= new Section(body
, SWT
.NO_FOCUS
, node
);
333 section
.setLayoutData(CmsUtils
.fillWidth());
334 section
.setLayout(CmsUtils
.noSpaceGridLayout());
339 protected void createSimpleLT(Composite bodyRow
, Node node
,
340 String propName
, String label
, String msg
)
341 throws RepositoryException
{
342 if (getCmsEditable().canEdit() || node
.hasProperty(propName
)) {
343 createPropertyLbl(bodyRow
, label
);
344 EditablePropertyString eps
= new EditablePropertyString(bodyRow
,
345 SWT
.WRAP
| SWT
.LEFT
, node
, propName
, msg
);
346 eps
.setMouseListener(getMouseListener());
347 eps
.setFocusListener(getFocusListener());
348 eps
.setLayoutData(CmsUtils
.fillWidth());
352 protected void createMultiStringLT(Composite bodyRow
, Node node
,
353 String propName
, String label
, String msg
)
354 throws RepositoryException
{
355 boolean canEdit
= getCmsEditable().canEdit();
356 if (canEdit
|| node
.hasProperty(propName
)) {
357 createPropertyLbl(bodyRow
, label
);
359 List
<String
> valueStrings
= new ArrayList
<String
>();
361 if (node
.hasProperty(propName
)) {
362 Value
[] values
= node
.getProperty(propName
).getValues();
363 for (Value value
: values
)
364 valueStrings
.add(value
.getString());
367 // TODO use a drop down to display possible values to the end user
368 EditableMultiStringProperty emsp
= new EditableMultiStringProperty(
369 bodyRow
, SWT
.SINGLE
| SWT
.LEAD
, node
, propName
,
370 valueStrings
, new String
[] { "Implement this" }, msg
,
371 canEdit ?
getRemoveValueSelListener() : null);
373 // emsp.setMouseListener(getMouseListener());
374 emsp
.setStyle(FormStyle
.propertyMessage
.style());
375 emsp
.setLayoutData(CmsUtils
.fillWidth());
379 protected Label
createPropertyLbl(Composite parent
, String value
) {
380 return createPropertyLbl(parent
, value
, SWT
.TOP
);
383 protected Label
createPropertyLbl(Composite parent
, String value
, int vAlign
) {
384 Label label
= new Label(parent
, SWT
.RIGHT
| SWT
.WRAP
);
385 label
.setText(value
+ " ");
386 CmsUtils
.style(label
, FormStyle
.propertyLabel
.style());
387 GridData gd
= new GridData(SWT
.RIGHT
, vAlign
, false, false);
388 gd
.widthHint
= labelColWidth
;
389 label
.setLayoutData(gd
);
393 protected Label
newStyledLabel(Composite parent
, String style
, String value
) {
394 Label label
= new Label(parent
, SWT
.NONE
);
395 label
.setText(value
);
396 CmsUtils
.style(label
, style
);
400 protected Composite
createRowLayoutComposite(Composite parent
)
401 throws RepositoryException
{
402 Composite bodyRow
= new Composite(parent
, SWT
.NO_FOCUS
);
403 bodyRow
.setLayoutData(CmsUtils
.fillWidth());
404 RowLayout rl
= new RowLayout(SWT
.WRAP
);
405 rl
.type
= SWT
.HORIZONTAL
;
406 rl
.spacing
= rowLayoutHSpacing
;
407 rl
.marginHeight
= rl
.marginWidth
= 0;
408 rl
.marginTop
= rl
.marginBottom
= rl
.marginLeft
= rl
.marginRight
= 0;
409 bodyRow
.setLayout(rl
);
413 protected Composite
createSectionBody(Composite parent
, int nbOfCol
) {
414 // The separator line. Ugly workaround that should be better managed via
416 Composite header
= new Composite(parent
, SWT
.NO_FOCUS
);
417 CmsUtils
.style(header
, FormStyle
.sectionHeader
.style());
418 GridData gd
= CmsUtils
.fillWidth();
419 gd
.verticalIndent
= sectionSeparatorHeight
;
421 header
.setLayoutData(gd
);
423 Composite bodyRow
= new Composite(parent
, SWT
.NO_FOCUS
);
424 gd
= CmsUtils
.fillWidth();
425 gd
.verticalIndent
= sectionBodyVIndent
;
426 bodyRow
.setLayoutData(gd
);
427 GridLayout gl
= new GridLayout(nbOfCol
, false);
428 gl
.horizontalSpacing
= sectionBodyHSpacing
;
429 gl
.verticalSpacing
= sectionBodyVSpacing
;
430 bodyRow
.setLayout(gl
);
431 CmsUtils
.style(bodyRow
, FormStyle
.section
.style());
436 protected Composite
createAddImgComposite(final Section section
,
437 Composite parent
, final Node parentNode
) throws RepositoryException
{
439 Composite body
= new Composite(parent
, SWT
.NO_FOCUS
);
440 body
.setLayout(new GridLayout());
442 FormFileUploadReceiver receiver
= new FormFileUploadReceiver(section
,
444 final FileUploadHandler currentUploadHandler
= new FileUploadHandler(
446 if (fileUploadListener
!= null)
447 currentUploadHandler
.addUploadListener(fileUploadListener
);
450 final FileUpload fileUpload
= new FileUpload(body
, SWT
.BORDER
);
451 fileUpload
.setText("Import an image");
452 fileUpload
.setLayoutData(new GridData(SWT
.CENTER
, SWT
.CENTER
, true,
454 fileUpload
.addSelectionListener(new SelectionAdapter() {
455 private static final long serialVersionUID
= 4869523412991968759L;
458 public void widgetSelected(SelectionEvent e
) {
459 ServerPushSession pushSession
= new ServerPushSession();
461 String uploadURL
= currentUploadHandler
.getUploadUrl();
462 fileUpload
.submit(uploadURL
);
469 protected class FormFileUploadReceiver
extends FileUploadReceiver
implements
472 private Node context
;
473 private Section section
;
476 public FormFileUploadReceiver(Section section
, Node context
, String name
) {
477 this.context
= context
;
478 this.section
= section
;
483 public void receive(InputStream stream
, FileDetails details
)
487 name
= details
.getFileName();
489 imageManager().uploadImage(context
, name
, stream
);
490 // TODO clean refresh strategy
491 section
.getDisplay().asyncExec(new Runnable() {
495 FormPageViewer
.this.refresh(section
);
497 section
.getParent().layout();
498 } catch (RepositoryException re
) {
499 throw new ArgeoException("unable to refresh "
500 + "image section for " + context
);
504 } catch (RepositoryException re
) {
505 throw new ArgeoException("unable to upload image " + name
511 protected void addListeners(StyledControl control
) {
512 control
.setMouseListener(getMouseListener());
513 control
.setFocusListener(getFocusListener());
516 protected Img
createImgComposite(Composite parent
, Node node
,
517 Point preferredSize
) throws RepositoryException
{
518 Img img
= new Img(parent
, SWT
.NONE
, node
, preferredSize
) {
519 private static final long serialVersionUID
= 1297900641952417540L;
522 protected void setContainerLayoutData(Composite composite
) {
523 composite
.setLayoutData(CmsUtils
.grabWidth(SWT
.CENTER
,
528 protected void setControlLayoutData(Control control
) {
529 control
.setLayoutData(CmsUtils
.grabWidth(SWT
.CENTER
,
533 img
.setLayoutData(CmsUtils
.grabWidth(SWT
.CENTER
, SWT
.DEFAULT
));
539 protected Composite
addDeleteAbility(final Section section
,
540 final Node sessionNode
, int topWeight
, int rightWeight
) {
541 Composite comp
= new Composite(section
, SWT
.NONE
);
542 comp
.setLayoutData(CmsUtils
.fillAll());
543 comp
.setLayout(new FormLayout());
545 // The body to be populated
546 Composite body
= new Composite(comp
, SWT
.NO_FOCUS
);
547 body
.setLayoutData(EclipseUiUtils
.fillFormData());
549 if (getCmsEditable().canEdit()) {
551 Button deleteBtn
= new Button(comp
, SWT
.FLAT
);
552 CmsUtils
.style(deleteBtn
, FormStyle
.deleteOverlay
.style());
553 FormData formData
= new FormData();
554 formData
.right
= new FormAttachment(rightWeight
, 0);
555 formData
.top
= new FormAttachment(topWeight
, 0);
556 deleteBtn
.setLayoutData(formData
);
557 deleteBtn
.moveAbove(body
);
559 deleteBtn
.addSelectionListener(new SelectionAdapter() {
560 private static final long serialVersionUID
= 4304223543657238462L;
563 public void widgetSelected(SelectionEvent e
) {
564 super.widgetSelected(e
);
565 if (MessageDialog
.openConfirm(section
.getShell(),
567 "Are you really you want to remove this?")) {
570 session
= sessionNode
.getSession();
571 Section parSection
= section
.getParentSection();
572 sessionNode
.remove();
576 } catch (RepositoryException re
) {
577 throw new ArgeoException("Unable to delete "
589 // LOCAL HELPERS FOR NODE MANAGEMENT
590 protected Node
getOrCreateNode(Node parent
, String nodeType
, String nodeName
)
591 throws RepositoryException
{
593 if (getCmsEditable().canEdit() && !parent
.hasNode(nodeName
)) {
594 node
= JcrUtils
.mkdirs(parent
, nodeName
, nodeType
);
595 parent
.getSession().save();
598 if (getCmsEditable().canEdit() || parent
.hasNode(nodeName
))
599 node
= parent
.getNode(nodeName
);
604 private SelectionListener
getRemoveValueSelListener() {
605 return new SelectionAdapter() {
606 private static final long serialVersionUID
= 9022259089907445195L;
609 public void widgetSelected(SelectionEvent e
) {
610 Object source
= e
.getSource();
611 if (source
instanceof Button
) {
612 Button btn
= (Button
) source
;
613 Object obj
= btn
.getData(FormConstants
.LINKED_VALUE
);
614 EditablePart ep
= findDataParent(btn
);
615 if (ep
!= null && ep
instanceof EditableMultiStringProperty
) {
616 EditableMultiStringProperty emsp
= (EditableMultiStringProperty
) ep
;
617 List
<String
> values
= emsp
.getValues();
618 if (values
.contains(obj
)) {
619 values
.remove(values
.indexOf(obj
));
620 emsp
.setValues(values
);
623 // TODO workaround to force refresh
626 } catch (RepositoryException e1
) {
627 throw new ArgeoException(
628 "Unable to remove value " + obj
, e1
);
638 protected void setPropertySilently(Node node
, String propName
, String value
)
639 throws RepositoryException
{
642 // Format strings to replace \n
643 value
= value
.replaceAll("\n", "<br/>");
644 // Do not make the update if validation fails
646 MarkupValidatorCopy
.getInstance().validate(value
);
647 } catch (Exception e
) {
648 log
.warn("Cannot set [" + value
+ "] on prop " + propName
649 + "of " + node
+ ", String cannot be validated - "
653 // TODO check if the newly created property is of the correct type,
654 // otherwise the property will be silently created with a STRING
656 node
.setProperty(propName
, value
);
657 } catch (ValueFormatException vfe
) {
658 log
.warn("Cannot set [" + value
+ "] on prop " + propName
+ "of "
659 + node
+ " - " + vfe
.getMessage());