1 package org
.argeo
.cms
.ui
.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
.cms
.ui
.CmsEditable
;
20 import org
.argeo
.cms
.ui
.CmsImageManager
;
21 import org
.argeo
.cms
.ui
.CmsView
;
22 import org
.argeo
.cms
.ui
.util
.CmsUiUtils
;
23 import org
.argeo
.cms
.ui
.viewers
.AbstractPageViewer
;
24 import org
.argeo
.cms
.ui
.viewers
.EditablePart
;
25 import org
.argeo
.cms
.ui
.viewers
.Section
;
26 import org
.argeo
.cms
.ui
.viewers
.SectionPart
;
27 import org
.argeo
.cms
.ui
.widgets
.EditableImage
;
28 import org
.argeo
.cms
.ui
.widgets
.Img
;
29 import org
.argeo
.cms
.ui
.widgets
.StyledControl
;
30 import org
.argeo
.eclipse
.ui
.EclipseUiUtils
;
31 import org
.argeo
.jcr
.JcrException
;
32 import org
.argeo
.jcr
.JcrUtils
;
33 import org
.eclipse
.jface
.dialogs
.MessageDialog
;
34 import org
.eclipse
.rap
.fileupload
.FileDetails
;
35 import org
.eclipse
.rap
.fileupload
.FileUploadEvent
;
36 import org
.eclipse
.rap
.fileupload
.FileUploadHandler
;
37 import org
.eclipse
.rap
.fileupload
.FileUploadListener
;
38 import org
.eclipse
.rap
.fileupload
.FileUploadReceiver
;
39 import org
.eclipse
.rap
.rwt
.service
.ServerPushSession
;
40 import org
.eclipse
.rap
.rwt
.widgets
.FileUpload
;
41 import org
.eclipse
.swt
.SWT
;
42 import org
.eclipse
.swt
.events
.FocusEvent
;
43 import org
.eclipse
.swt
.events
.FocusListener
;
44 import org
.eclipse
.swt
.events
.MouseAdapter
;
45 import org
.eclipse
.swt
.events
.MouseEvent
;
46 import org
.eclipse
.swt
.events
.MouseListener
;
47 import org
.eclipse
.swt
.events
.SelectionAdapter
;
48 import org
.eclipse
.swt
.events
.SelectionEvent
;
49 import org
.eclipse
.swt
.events
.SelectionListener
;
50 import org
.eclipse
.swt
.graphics
.Point
;
51 import org
.eclipse
.swt
.layout
.FormAttachment
;
52 import org
.eclipse
.swt
.layout
.FormData
;
53 import org
.eclipse
.swt
.layout
.FormLayout
;
54 import org
.eclipse
.swt
.layout
.GridData
;
55 import org
.eclipse
.swt
.layout
.GridLayout
;
56 import org
.eclipse
.swt
.layout
.RowLayout
;
57 import org
.eclipse
.swt
.widgets
.Button
;
58 import org
.eclipse
.swt
.widgets
.Composite
;
59 import org
.eclipse
.swt
.widgets
.Control
;
60 import org
.eclipse
.swt
.widgets
.Label
;
61 import org
.eclipse
.swt
.widgets
.Text
;
63 /** Manage life cycle of a form page that is linked to a given node */
64 public class FormPageViewer
extends AbstractPageViewer
{
65 private final static Log log
= LogFactory
.getLog(FormPageViewer
.class);
66 private static final long serialVersionUID
= 5277789504209413500L;
68 private final Section mainSection
;
70 // TODO manage within the CSS
71 private Integer labelColWidth
= null;
72 private int rowLayoutHSpacing
= 8;
74 // Context cached in the viewer
75 // The reference to translate from text to calendar and reverse
76 private DateFormat dateFormat
= new SimpleDateFormat(FormUtils
.DEFAULT_SHORT_DATE_FORMAT
);
77 private CmsImageManager imageManager
;
78 private FileUploadListener fileUploadListener
;
80 public FormPageViewer(Section mainSection
, int style
, CmsEditable cmsEditable
) throws RepositoryException
{
81 super(mainSection
, style
, cmsEditable
);
82 this.mainSection
= mainSection
;
84 if (getCmsEditable().canEdit()) {
85 fileUploadListener
= new FUL();
90 protected void prepare(EditablePart part
, Object caretPosition
) {
91 if (part
instanceof Img
) {
92 ((Img
) part
).setFileUploadListener(fileUploadListener
);
96 /** To be overridden.Save the edited part. */
97 protected void save(EditablePart part
) throws RepositoryException
{
99 if (part
instanceof EditableMultiStringProperty
) {
100 EditableMultiStringProperty ept
= (EditableMultiStringProperty
) part
;
102 List
<String
> values
= ept
.getValues();
104 node
= ept
.getNode();
105 String propName
= ept
.getPropertyName();
106 if (values
.isEmpty()) {
107 if (node
.hasProperty(propName
))
108 node
.getProperty(propName
).remove();
110 node
.setProperty(propName
, values
.toArray(new String
[0]));
112 // => Viewer : Controller
113 } else if (part
instanceof EditablePropertyString
) {
114 EditablePropertyString ept
= (EditablePropertyString
) part
;
116 String txt
= ((Text
) ept
.getControl()).getText();
118 node
= ept
.getNode();
119 String propName
= ept
.getPropertyName();
120 if (EclipseUiUtils
.isEmpty(txt
)) {
121 if (node
.hasProperty(propName
))
122 node
.getProperty(propName
).remove();
124 setPropertySilently(node
, propName
, txt
);
125 // node.setProperty(propName, txt);
127 // node.getSession().save();
128 // => Viewer : Controller
129 } else if (part
instanceof EditablePropertyDate
) {
130 EditablePropertyDate ept
= (EditablePropertyDate
) part
;
131 Calendar cal
= FormUtils
.parseDate(dateFormat
, ((Text
) ept
.getControl()).getText());
132 node
= ept
.getNode();
133 String propName
= ept
.getPropertyName();
135 if (node
.hasProperty(propName
))
136 node
.getProperty(propName
).remove();
138 node
.setProperty(propName
, cal
);
140 // node.getSession().save();
141 // => Viewer : Controller
143 // TODO: make this configurable, sometimes we do not want to save the
144 // current session at this stage
145 if (node
!= null && node
.getSession().hasPendingChanges()) {
146 JcrUtils
.updateLastModified(node
, true);
147 node
.getSession().save();
152 protected void updateContent(EditablePart part
) throws RepositoryException
{
153 if (part
instanceof EditableMultiStringProperty
) {
154 EditableMultiStringProperty ept
= (EditableMultiStringProperty
) part
;
156 Node node
= ept
.getNode();
157 String propName
= ept
.getPropertyName();
158 List
<String
> valStrings
= new ArrayList
<String
>();
159 if (node
.hasProperty(propName
)) {
160 Value
[] values
= node
.getProperty(propName
).getValues();
161 for (Value val
: values
)
162 valStrings
.add(val
.getString());
164 ept
.setValues(valStrings
);
165 } else if (part
instanceof EditablePropertyString
) {
166 // || part instanceof EditableLink
167 EditablePropertyString ept
= (EditablePropertyString
) part
;
169 Node node
= ept
.getNode();
170 String propName
= ept
.getPropertyName();
171 if (node
.hasProperty(propName
)) {
172 String value
= node
.getProperty(propName
).getString();
176 // => Viewer : Controller
177 } else if (part
instanceof EditablePropertyDate
) {
178 EditablePropertyDate ept
= (EditablePropertyDate
) part
;
180 Node node
= ept
.getNode();
181 String propName
= ept
.getPropertyName();
182 if (node
.hasProperty(propName
))
183 ept
.setText(dateFormat
.format(node
.getProperty(propName
).getDate().getTime()));
186 } else if (part
instanceof SectionPart
) {
187 SectionPart sectionPart
= (SectionPart
) part
;
188 Node partNode
= sectionPart
.getNode();
189 // use control AFTER setting style, since it may have been reset
190 if (part
instanceof EditableImage
) {
191 EditableImage editableImage
= (EditableImage
) part
;
192 imageManager().load(partNode
, part
.getControl(), editableImage
.getPreferredImageSize());
197 // FILE UPLOAD LISTENER
198 protected class FUL
implements FileUploadListener
{
203 public void uploadProgress(FileUploadEvent event
) {
204 // TODO Monitor upload progress
207 public void uploadFailed(FileUploadEvent event
) {
208 throw new IllegalStateException("Upload failed " + event
, event
.getException());
211 public void uploadFinished(FileUploadEvent event
) {
212 for (FileDetails file
: event
.getFileDetails()) {
213 if (log
.isDebugEnabled())
214 log
.debug("Received: " + file
.getFileName());
216 mainSection
.getDisplay().syncExec(new Runnable() {
222 FileUploadHandler uploadHandler
= (FileUploadHandler
) event
.getSource();
223 uploadHandler
.dispose();
227 // FOCUS OUT LISTENER
228 protected FocusListener
createFocusListener() {
229 return new FocusOutListener();
232 private class FocusOutListener
implements FocusListener
{
233 private static final long serialVersionUID
= -6069205786732354186L;
236 public void focusLost(FocusEvent event
) {
241 public void focusGained(FocusEvent event
) {
248 protected MouseListener
createMouseListener() {
252 private class ML
extends MouseAdapter
{
253 private static final long serialVersionUID
= 8526890859876770905L;
256 public void mouseDoubleClick(MouseEvent e
) {
258 Control source
= (Control
) e
.getSource();
259 if (getCmsEditable().canEdit()) {
260 if (getCmsEditable().isEditing() && !(getEdited() instanceof Img
)) {
261 if (source
== mainSection
)
263 EditablePart part
= findDataParent(source
);
266 getCmsEditable().startEditing();
273 public void mouseDown(MouseEvent e
) {
274 if (getCmsEditable().isEditing()) {
276 Control source
= (Control
) e
.getSource();
277 EditablePart composite
= findDataParent(source
);
278 Point point
= new Point(e
.x
, e
.y
);
279 if (!(composite
instanceof Img
))
280 edit(composite
, source
.toDisplay(point
));
281 } else if (e
.button
== 3) {
282 // EditablePart composite = findDataParent((Control) e
284 // if (styledTools != null)
285 // styledTools.show(composite, new Point(e.x, e.y));
290 protected synchronized void upload(EditablePart part
) {
291 if (part
instanceof SectionPart
) {
292 if (part
instanceof Img
) {
293 if (getEdited() == part
)
296 layout(part
.getControl());
303 public Control
getControl() {
307 protected CmsImageManager
imageManager() {
308 if (imageManager
== null)
309 imageManager
= CmsView
.getCmsView(mainSection
).getImageManager();
314 protected Section
createSectionIfNeeded(Composite body
, Node node
) throws RepositoryException
{
315 Section section
= null;
317 section
= new Section(body
, SWT
.NO_FOCUS
, node
);
318 section
.setLayoutData(CmsUiUtils
.fillWidth());
319 section
.setLayout(CmsUiUtils
.noSpaceGridLayout());
324 protected void createSimpleLT(Composite bodyRow
, Node node
, String propName
, String label
, String msg
)
325 throws RepositoryException
{
326 if (getCmsEditable().canEdit() || node
.hasProperty(propName
)) {
327 createPropertyLbl(bodyRow
, label
);
328 EditablePropertyString eps
= new EditablePropertyString(bodyRow
, SWT
.WRAP
| SWT
.LEFT
, node
, propName
, msg
);
329 eps
.setMouseListener(getMouseListener());
330 eps
.setFocusListener(getFocusListener());
331 eps
.setLayoutData(CmsUiUtils
.fillWidth());
335 protected void createMultiStringLT(Composite bodyRow
, Node node
, String propName
, String label
, String msg
)
336 throws RepositoryException
{
337 boolean canEdit
= getCmsEditable().canEdit();
338 if (canEdit
|| node
.hasProperty(propName
)) {
339 createPropertyLbl(bodyRow
, label
);
341 List
<String
> valueStrings
= new ArrayList
<String
>();
343 if (node
.hasProperty(propName
)) {
344 Value
[] values
= node
.getProperty(propName
).getValues();
345 for (Value value
: values
)
346 valueStrings
.add(value
.getString());
349 // TODO use a drop down to display possible values to the end user
350 EditableMultiStringProperty emsp
= new EditableMultiStringProperty(bodyRow
, SWT
.SINGLE
| SWT
.LEAD
, node
,
351 propName
, valueStrings
, new String
[] { "Implement this" }, msg
,
352 canEdit ?
getRemoveValueSelListener() : null);
354 // emsp.setMouseListener(getMouseListener());
355 emsp
.setStyle(FormStyle
.propertyMessage
.style());
356 emsp
.setLayoutData(CmsUiUtils
.fillWidth());
360 protected Label
createPropertyLbl(Composite parent
, String value
) {
361 return createPropertyLbl(parent
, value
, SWT
.NONE
);
364 protected Label
createPropertyLbl(Composite parent
, String value
, int vAlign
) {
365 // boolean isSmall = CmsView.getCmsView(parent).getUxContext().isSmall();
366 Label label
= new Label(parent
, SWT
.LEAD
| SWT
.WRAP
);
367 label
.setText(value
+ " ");
368 CmsUiUtils
.style(label
, FormStyle
.propertyLabel
.style());
369 GridData gd
= new GridData(SWT
.LEAD
, vAlign
, false, false);
370 if (labelColWidth
!= null)
371 gd
.widthHint
= labelColWidth
;
372 label
.setLayoutData(gd
);
376 protected Label
newStyledLabel(Composite parent
, String style
, String value
) {
377 Label label
= new Label(parent
, SWT
.NONE
);
378 label
.setText(value
);
379 CmsUiUtils
.style(label
, style
);
383 protected Composite
createRowLayoutComposite(Composite parent
) throws RepositoryException
{
384 Composite bodyRow
= new Composite(parent
, SWT
.NO_FOCUS
);
385 bodyRow
.setLayoutData(CmsUiUtils
.fillWidth());
386 RowLayout rl
= new RowLayout(SWT
.WRAP
);
387 rl
.type
= SWT
.HORIZONTAL
;
388 rl
.spacing
= rowLayoutHSpacing
;
389 rl
.marginHeight
= rl
.marginWidth
= 0;
390 rl
.marginTop
= rl
.marginBottom
= rl
.marginLeft
= rl
.marginRight
= 0;
391 bodyRow
.setLayout(rl
);
395 protected Composite
createAddImgComposite(final Section section
, Composite parent
, final Node parentNode
)
396 throws RepositoryException
{
398 Composite body
= new Composite(parent
, SWT
.NO_FOCUS
);
399 body
.setLayout(new GridLayout());
401 FormFileUploadReceiver receiver
= new FormFileUploadReceiver(section
, parentNode
, null);
402 final FileUploadHandler currentUploadHandler
= new FileUploadHandler(receiver
);
403 if (fileUploadListener
!= null)
404 currentUploadHandler
.addUploadListener(fileUploadListener
);
407 final FileUpload fileUpload
= new FileUpload(body
, SWT
.BORDER
);
408 fileUpload
.setText("Import an image");
409 fileUpload
.setLayoutData(new GridData(SWT
.CENTER
, SWT
.CENTER
, true, true));
410 fileUpload
.addSelectionListener(new SelectionAdapter() {
411 private static final long serialVersionUID
= 4869523412991968759L;
414 public void widgetSelected(SelectionEvent e
) {
415 ServerPushSession pushSession
= new ServerPushSession();
417 String uploadURL
= currentUploadHandler
.getUploadUrl();
418 fileUpload
.submit(uploadURL
);
425 protected class FormFileUploadReceiver
extends FileUploadReceiver
{
427 private Node context
;
428 private Section section
;
431 public FormFileUploadReceiver(Section section
, Node context
, String name
) {
432 this.context
= context
;
433 this.section
= section
;
438 public void receive(InputStream stream
, FileDetails details
) throws IOException
{
441 name
= details
.getFileName();
443 // TODO clean image name more carefully
444 String cleanedName
= name
.replaceAll("[^a-zA-Z0-9-.]", "");
445 // We add a unique prefix to workaround the cache issue: when
446 // deleting and re-adding a new image with same name, the end user
447 // browser will use the cache and the image will remain unchanged
449 cleanedName
= System
.currentTimeMillis() % 100000 + "_" + cleanedName
;
452 imageManager().uploadImage(context
, context
, cleanedName
, stream
, details
.getContentType());
453 // TODO clean refresh strategy
454 section
.getDisplay().asyncExec(new Runnable() {
458 FormPageViewer
.this.refresh(section
);
460 section
.getParent().layout();
461 } catch (RepositoryException re
) {
462 throw new JcrException("Unable to refresh " + "image section for " + context
, re
);
466 } catch (RepositoryException re
) {
467 throw new JcrException("unable to upload image " + name
+ " at " + context
, re
);
472 protected void addListeners(StyledControl control
) {
473 control
.setMouseListener(getMouseListener());
474 control
.setFocusListener(getFocusListener());
477 protected Img
createImgComposite(Composite parent
, Node node
, Point preferredSize
) throws RepositoryException
{
478 Img img
= new Img(parent
, SWT
.NONE
, node
, preferredSize
) {
479 private static final long serialVersionUID
= 1297900641952417540L;
482 protected void setContainerLayoutData(Composite composite
) {
483 composite
.setLayoutData(CmsUiUtils
.grabWidth(SWT
.CENTER
, SWT
.DEFAULT
));
487 protected void setControlLayoutData(Control control
) {
488 control
.setLayoutData(CmsUiUtils
.grabWidth(SWT
.CENTER
, SWT
.DEFAULT
));
491 img
.setLayoutData(CmsUiUtils
.grabWidth(SWT
.CENTER
, SWT
.DEFAULT
));
497 protected Composite
addDeleteAbility(final Section section
, final Node sessionNode
, int topWeight
,
499 Composite comp
= new Composite(section
, SWT
.NONE
);
500 comp
.setLayoutData(CmsUiUtils
.fillAll());
501 comp
.setLayout(new FormLayout());
503 // The body to be populated
504 Composite body
= new Composite(comp
, SWT
.NO_FOCUS
);
505 body
.setLayoutData(EclipseUiUtils
.fillFormData());
507 if (getCmsEditable().canEdit()) {
509 Button deleteBtn
= new Button(comp
, SWT
.FLAT
);
510 CmsUiUtils
.style(deleteBtn
, FormStyle
.deleteOverlay
.style());
511 FormData formData
= new FormData();
512 formData
.right
= new FormAttachment(rightWeight
, 0);
513 formData
.top
= new FormAttachment(topWeight
, 0);
514 deleteBtn
.setLayoutData(formData
);
515 deleteBtn
.moveAbove(body
);
517 deleteBtn
.addSelectionListener(new SelectionAdapter() {
518 private static final long serialVersionUID
= 4304223543657238462L;
521 public void widgetSelected(SelectionEvent e
) {
522 super.widgetSelected(e
);
523 if (MessageDialog
.openConfirm(section
.getShell(), "Confirm deletion",
524 "Are you really you want to remove this?")) {
527 session
= sessionNode
.getSession();
528 Section parSection
= section
.getParentSection();
529 sessionNode
.remove();
533 } catch (RepositoryException re
) {
534 throw new JcrException("Unable to delete " + sessionNode
, re
);
545 // // LOCAL HELPERS FOR NODE MANAGEMENT
546 // private Node getOrCreateNode(Node parent, String nodeName, String nodeType) throws RepositoryException {
548 // if (getCmsEditable().canEdit() && !parent.hasNode(nodeName)) {
549 // node = JcrUtils.mkdirs(parent, nodeName, nodeType);
550 // parent.getSession().save();
553 // if (getCmsEditable().canEdit() || parent.hasNode(nodeName))
554 // node = parent.getNode(nodeName);
559 private SelectionListener
getRemoveValueSelListener() {
560 return new SelectionAdapter() {
561 private static final long serialVersionUID
= 9022259089907445195L;
564 public void widgetSelected(SelectionEvent e
) {
565 Object source
= e
.getSource();
566 if (source
instanceof Button
) {
567 Button btn
= (Button
) source
;
568 Object obj
= btn
.getData(FormConstants
.LINKED_VALUE
);
569 EditablePart ep
= findDataParent(btn
);
570 if (ep
!= null && ep
instanceof EditableMultiStringProperty
) {
571 EditableMultiStringProperty emsp
= (EditableMultiStringProperty
) ep
;
572 List
<String
> values
= emsp
.getValues();
573 if (values
.contains(obj
)) {
574 values
.remove(values
.indexOf(obj
));
575 emsp
.setValues(values
);
578 // TODO workaround to force refresh
581 } catch (RepositoryException e1
) {
582 throw new JcrException("Unable to remove value " + obj
, e1
);
592 protected void setPropertySilently(Node node
, String propName
, String value
) throws RepositoryException
{
595 // Format strings to replace \n
596 value
= value
.replaceAll("\n", "<br/>");
597 // Do not make the update if validation fails
599 MarkupValidatorCopy
.getInstance().validate(value
);
600 } catch (Exception e
) {
601 log
.warn("Cannot set [" + value
+ "] on prop " + propName
+ "of " + node
602 + ", String cannot be validated - " + e
.getMessage());
605 // TODO check if the newly created property is of the correct type,
606 // otherwise the property will be silently created with a STRING
608 node
.setProperty(propName
, value
);
609 } catch (ValueFormatException vfe
) {
610 log
.warn("Cannot set [" + value
+ "] on prop " + propName
+ "of " + node
+ " - " + vfe
.getMessage());