1 package org
.argeo
.app
.swt
.forms
;
3 import java
.io
.IOException
;
4 import java
.io
.InputStream
;
5 import java
.time
.Instant
;
6 import java
.time
.format
.DateTimeFormatter
;
7 import java
.time
.temporal
.TemporalAccessor
;
8 import java
.util
.ArrayList
;
11 import javax
.xml
.namespace
.QName
;
13 import org
.argeo
.api
.acr
.Content
;
14 import org
.argeo
.api
.cms
.CmsLog
;
15 import org
.argeo
.api
.cms
.ux
.Cms2DSize
;
16 import org
.argeo
.api
.cms
.ux
.CmsEditable
;
17 import org
.argeo
.api
.cms
.ux
.CmsImageManager
;
18 import org
.argeo
.cms
.swt
.CmsSwtUtils
;
19 import org
.argeo
.cms
.swt
.SwtEditablePart
;
20 import org
.argeo
.cms
.swt
.acr
.AbstractPageViewer
;
21 import org
.argeo
.cms
.swt
.acr
.Img
;
22 import org
.argeo
.cms
.swt
.acr
.SwtSection
;
23 import org
.argeo
.cms
.swt
.acr
.SwtSectionPart
;
24 import org
.argeo
.cms
.swt
.widgets
.StyledControl
;
25 import org
.argeo
.eclipse
.ui
.EclipseUiUtils
;
26 import org
.eclipse
.rap
.fileupload
.FileDetails
;
27 import org
.eclipse
.rap
.fileupload
.FileUploadEvent
;
28 import org
.eclipse
.rap
.fileupload
.FileUploadHandler
;
29 import org
.eclipse
.rap
.fileupload
.FileUploadListener
;
30 import org
.eclipse
.rap
.fileupload
.FileUploadReceiver
;
31 import org
.eclipse
.rap
.rwt
.service
.ServerPushSession
;
32 import org
.eclipse
.rap
.rwt
.widgets
.FileUpload
;
33 import org
.eclipse
.swt
.SWT
;
34 import org
.eclipse
.swt
.events
.FocusEvent
;
35 import org
.eclipse
.swt
.events
.FocusListener
;
36 import org
.eclipse
.swt
.events
.MouseAdapter
;
37 import org
.eclipse
.swt
.events
.MouseEvent
;
38 import org
.eclipse
.swt
.events
.MouseListener
;
39 import org
.eclipse
.swt
.events
.SelectionAdapter
;
40 import org
.eclipse
.swt
.events
.SelectionEvent
;
41 import org
.eclipse
.swt
.events
.SelectionListener
;
42 import org
.eclipse
.swt
.graphics
.Point
;
43 import org
.eclipse
.swt
.layout
.FormAttachment
;
44 import org
.eclipse
.swt
.layout
.FormData
;
45 import org
.eclipse
.swt
.layout
.FormLayout
;
46 import org
.eclipse
.swt
.layout
.GridData
;
47 import org
.eclipse
.swt
.layout
.GridLayout
;
48 import org
.eclipse
.swt
.layout
.RowLayout
;
49 import org
.eclipse
.swt
.widgets
.Button
;
50 import org
.eclipse
.swt
.widgets
.Composite
;
51 import org
.eclipse
.swt
.widgets
.Control
;
52 import org
.eclipse
.swt
.widgets
.Label
;
53 import org
.eclipse
.swt
.widgets
.Text
;
55 /** Manage life cycle of a form page that is linked to a given node */
56 public class FormPageViewer
extends AbstractPageViewer
{
57 private final static CmsLog log
= CmsLog
.getLog(FormPageViewer
.class);
59 private final SwtSection mainSection
;
61 // TODO manage within the CSS
62 private Integer labelColWidth
= null;
63 private int rowLayoutHSpacing
= 8;
65 // Context cached in the viewer
66 // The reference to translate from text to calendar and reverse
67 private DateTimeFormatter dateFormat
= DateTimeFormatter
.ofPattern(FormUtils
.DEFAULT_SHORT_DATE_FORMAT
);
68 // new SimpleDateFormat(FormUtils.DEFAULT_SHORT_DATE_FORMAT);
69 private CmsImageManager
<Control
, Content
> imageManager
;
70 private FileUploadListener fileUploadListener
;
72 public FormPageViewer(SwtSection mainSection
, int style
, CmsEditable cmsEditable
) {
73 super(mainSection
, style
, cmsEditable
);
74 this.mainSection
= mainSection
;
76 if (getCmsEditable().canEdit()) {
77 fileUploadListener
= new FUL();
82 protected void prepare(SwtEditablePart part
, Object caretPosition
) {
83 if (part
instanceof Img
) {
84 // ((Img) part).setFileUploadListener(fileUploadListener);
88 /** To be overridden.Save the edited part. */
89 protected void save(SwtEditablePart part
) {
91 if (part
instanceof EditableMultiStringProperty ept
) {
92 List
<String
> values
= ept
.getValues();
93 node
= ept
.getContent();
94 QName propName
= ept
.getPropertyName();
95 if (values
.isEmpty()) {
96 if (node
.containsKey(propName
))
97 node
.remove(propName
);
99 node
.put(propName
, values
);
100 // node.setProperty(propName, values.toArray(new String[0]));
102 // => Viewer : Controller
103 } else if (part
instanceof EditablePropertyString ept
) {
104 String txt
= ((Text
) ept
.getControl()).getText();
105 node
= ept
.getContent();
106 QName propName
= ept
.getPropertyName();
107 if (EclipseUiUtils
.isEmpty(txt
)) {
108 node
.remove(propName
);
110 setPropertySilently(node
, propName
, txt
);
111 // node.setProperty(propName, txt);
113 // node.getSession().save();
114 // => Viewer : Controller
115 } else if (part
instanceof EditablePropertyDate
) {
116 EditablePropertyDate ept
= (EditablePropertyDate
) part
;
117 // FIXME deal with no value set
118 TemporalAccessor cal
= FormUtils
.parseDate(dateFormat
, ((Text
) ept
.getControl()).getText());
119 node
= ept
.getContent();
120 QName propName
= ept
.getPropertyName();
122 node
.remove(propName
);
124 node
.put(propName
, cal
);
126 // node.getSession().save();
127 // => Viewer : Controller
129 // TODO: make this configurable, sometimes we do not want to save the
130 // current session at this stage
131 // if (node != null && node.getSession().hasPendingChanges()) {
132 // JcrUtils.updateLastModified(node, true);
133 // node.getSession().save();
138 protected void updateContent(SwtEditablePart part
) {
139 if (part
instanceof EditableMultiStringProperty ept
) {
140 Content node
= ept
.getContent();
141 QName propName
= ept
.getPropertyName();
142 List
<String
> valStrings
= new ArrayList
<String
>();
143 if (node
.containsKey(propName
)) {
144 for (String val
: node
.getMultiple(propName
, String
.class))
147 ept
.setValues(valStrings
);
148 } else if (part
instanceof EditablePropertyString ept
) {
149 // || part instanceof EditableLink
150 Content node
= ept
.getContent();
151 QName propName
= ept
.getPropertyName();
152 ept
.setText(node
.get(propName
, String
.class).orElse(""));
153 } else if (part
instanceof EditablePropertyDate ept
) {
154 Content node
= ept
.getContent();
155 QName propName
= ept
.getPropertyName();
156 if (node
.containsKey(propName
))
157 ept
.setText(dateFormat
.format(node
.get(propName
, Instant
.class).get()));
160 } else if (part
instanceof SwtSectionPart sectionPart
) {
161 Content partNode
= sectionPart
.getContent();
162 // use control AFTER setting style, since it may have been reset
163 if (part
instanceof Img
) {
164 Img editableImage
= (Img
) part
;
165 imageManager().load(partNode
, part
.getControl(), editableImage
.getPreferredImageSize(), null);
170 // FILE UPLOAD LISTENER
171 protected class FUL
implements FileUploadListener
{
176 public void uploadProgress(FileUploadEvent event
) {
177 // TODO Monitor upload progress
180 public void uploadFailed(FileUploadEvent event
) {
181 throw new IllegalStateException("Upload failed " + event
, event
.getException());
184 public void uploadFinished(FileUploadEvent event
) {
185 for (FileDetails file
: event
.getFileDetails()) {
186 if (log
.isDebugEnabled())
187 log
.debug("Received: " + file
.getFileName());
189 mainSection
.getDisplay().syncExec(new Runnable() {
195 FileUploadHandler uploadHandler
= (FileUploadHandler
) event
.getSource();
196 uploadHandler
.dispose();
200 // FOCUS OUT LISTENER
201 protected FocusListener
createFocusListener() {
202 return new FocusOutListener();
205 private class FocusOutListener
implements FocusListener
{
206 private static final long serialVersionUID
= -6069205786732354186L;
209 public void focusLost(FocusEvent event
) {
214 public void focusGained(FocusEvent event
) {
221 protected MouseListener
createMouseListener() {
225 private class ML
extends MouseAdapter
{
226 private static final long serialVersionUID
= 8526890859876770905L;
229 public void mouseDoubleClick(MouseEvent e
) {
231 Control source
= (Control
) e
.getSource();
232 if (getCmsEditable().canEdit()) {
233 if (getCmsEditable().isEditing() && !(getEdited() instanceof Img
)) {
234 if (source
== mainSection
)
236 SwtEditablePart part
= findDataParent(source
);
239 getCmsEditable().startEditing();
246 public void mouseDown(MouseEvent e
) {
247 if (getCmsEditable().isEditing()) {
249 Control source
= (Control
) e
.getSource();
250 SwtEditablePart composite
= findDataParent(source
);
251 Point point
= new Point(e
.x
, e
.y
);
252 if (!(composite
instanceof Img
))
253 edit(composite
, source
.toDisplay(point
));
254 } else if (e
.button
== 3) {
255 // EditablePart composite = findDataParent((Control) e
257 // if (styledTools != null)
258 // styledTools.show(composite, new Point(e.x, e.y));
263 protected synchronized void upload(SwtEditablePart part
) {
264 if (part
instanceof SwtSectionPart
) {
265 if (part
instanceof Img
) {
266 if (getEdited() == part
)
269 layout(part
.getControl());
276 public Control
getControl() {
280 protected CmsImageManager
<Control
, Content
> imageManager() {
281 if (imageManager
== null)
282 imageManager
= CmsSwtUtils
.getCmsView(mainSection
).getImageManager();
287 protected SwtSection
createSectionIfNeeded(Composite body
, Content node
) {
288 SwtSection section
= null;
290 section
= new SwtSection(body
, SWT
.NO_FOCUS
, node
);
291 section
.setLayoutData(CmsSwtUtils
.fillWidth());
292 section
.setLayout(CmsSwtUtils
.noSpaceGridLayout());
297 protected void createSimpleLT(Composite bodyRow
, Content node
, QName propName
, String label
, String msg
) {
298 if (getCmsEditable().canEdit() || node
.containsKey(propName
)) {
299 createPropertyLbl(bodyRow
, label
);
300 EditablePropertyString eps
= new EditablePropertyString(bodyRow
, SWT
.WRAP
| SWT
.LEFT
, node
, propName
, msg
);
301 eps
.setMouseListener(getMouseListener());
302 eps
.setFocusListener(getFocusListener());
303 eps
.setLayoutData(CmsSwtUtils
.fillWidth());
307 protected void createMultiStringLT(Composite bodyRow
, Content node
, QName propName
, String label
, String msg
) {
308 boolean canEdit
= getCmsEditable().canEdit();
309 if (canEdit
|| node
.containsKey(propName
)) {
310 createPropertyLbl(bodyRow
, label
);
312 List
<String
> valueStrings
= new ArrayList
<String
>();
314 if (node
.containsKey(propName
)) {
315 for (String value
: node
.getMultiple(propName
, String
.class))
316 valueStrings
.add(value
);
319 // TODO use a drop down to display possible values to the end user
320 EditableMultiStringProperty emsp
= new EditableMultiStringProperty(bodyRow
, SWT
.SINGLE
| SWT
.LEAD
, node
,
321 propName
, valueStrings
, new String
[] { "Implement this" }, msg
,
322 canEdit ?
getRemoveValueSelListener() : null);
324 // emsp.setMouseListener(getMouseListener());
325 emsp
.setStyle(FormStyle
.propertyMessage
.style());
326 emsp
.setLayoutData(CmsSwtUtils
.fillWidth());
330 protected Label
createPropertyLbl(Composite parent
, String value
) {
331 return createPropertyLbl(parent
, value
, SWT
.NONE
);
334 protected Label
createPropertyLbl(Composite parent
, String value
, int vAlign
) {
335 // boolean isSmall = CmsView.getCmsView(parent).getUxContext().isSmall();
336 Label label
= new Label(parent
, SWT
.LEAD
| SWT
.WRAP
);
337 label
.setText(value
+ " ");
338 CmsSwtUtils
.style(label
, FormStyle
.propertyLabel
.style());
339 GridData gd
= new GridData(SWT
.LEAD
, vAlign
, false, false);
340 if (labelColWidth
!= null)
341 gd
.widthHint
= labelColWidth
;
342 label
.setLayoutData(gd
);
346 protected Label
newStyledLabel(Composite parent
, String style
, String value
) {
347 Label label
= new Label(parent
, SWT
.NONE
);
348 label
.setText(value
);
349 CmsSwtUtils
.style(label
, style
);
353 protected Composite
createRowLayoutComposite(Composite parent
) {
354 Composite bodyRow
= new Composite(parent
, SWT
.NO_FOCUS
);
355 bodyRow
.setLayoutData(CmsSwtUtils
.fillWidth());
356 RowLayout rl
= new RowLayout(SWT
.WRAP
);
357 rl
.type
= SWT
.HORIZONTAL
;
358 rl
.spacing
= rowLayoutHSpacing
;
359 rl
.marginHeight
= rl
.marginWidth
= 0;
360 rl
.marginTop
= rl
.marginBottom
= rl
.marginLeft
= rl
.marginRight
= 0;
361 bodyRow
.setLayout(rl
);
365 protected Composite
createAddImgComposite(SwtSection section
, Composite parent
, Content parentNode
) {
367 Composite body
= new Composite(parent
, SWT
.NO_FOCUS
);
368 body
.setLayout(new GridLayout());
370 FormFileUploadReceiver receiver
= new FormFileUploadReceiver(section
, parentNode
, null);
371 final FileUploadHandler currentUploadHandler
= new FileUploadHandler(receiver
);
372 if (fileUploadListener
!= null)
373 currentUploadHandler
.addUploadListener(fileUploadListener
);
376 final FileUpload fileUpload
= new FileUpload(body
, SWT
.BORDER
);
377 fileUpload
.setText("Import an image");
378 fileUpload
.setLayoutData(new GridData(SWT
.CENTER
, SWT
.CENTER
, true, true));
379 fileUpload
.addSelectionListener(new SelectionAdapter() {
380 private static final long serialVersionUID
= 4869523412991968759L;
383 public void widgetSelected(SelectionEvent e
) {
384 ServerPushSession pushSession
= new ServerPushSession();
386 String uploadURL
= currentUploadHandler
.getUploadUrl();
387 fileUpload
.submit(uploadURL
);
394 protected class FormFileUploadReceiver
extends FileUploadReceiver
{
396 private Content context
;
397 private SwtSection section
;
400 public FormFileUploadReceiver(SwtSection section
, Content context
, String name
) {
401 this.context
= context
;
402 this.section
= section
;
407 public void receive(InputStream stream
, FileDetails details
) throws IOException
{
410 name
= details
.getFileName();
412 // TODO clean image name more carefully
413 String cleanedName
= name
.replaceAll("[^a-zA-Z0-9-.]", "");
414 // We add a unique prefix to workaround the cache issue: when
415 // deleting and re-adding a new image with same name, the end user
416 // browser will use the cache and the image will remain unchanged
418 cleanedName
= System
.currentTimeMillis() % 100000 + "_" + cleanedName
;
420 imageManager().uploadImage(context
, context
, cleanedName
, stream
, details
.getContentType());
421 // TODO clean refresh strategy
422 section
.getDisplay().asyncExec(new Runnable() {
425 FormPageViewer
.this.refresh(section
);
427 section
.getParent().layout();
433 protected void addListeners(StyledControl control
) {
434 control
.setMouseListener(getMouseListener());
435 control
.setFocusListener(getFocusListener());
438 protected Img
createImgComposite(Composite parent
, Content node
, Point preferredSize
) {
439 Img img
= new Img(parent
, SWT
.NONE
, node
, new Cms2DSize(preferredSize
.x
, preferredSize
.y
)) {
440 private static final long serialVersionUID
= 1297900641952417540L;
443 protected void setContainerLayoutData(Composite composite
) {
444 composite
.setLayoutData(CmsSwtUtils
.grabWidth(SWT
.CENTER
, SWT
.DEFAULT
));
448 protected void setControlLayoutData(Control control
) {
449 control
.setLayoutData(CmsSwtUtils
.grabWidth(SWT
.CENTER
, SWT
.DEFAULT
));
452 img
.setLayoutData(CmsSwtUtils
.grabWidth(SWT
.CENTER
, SWT
.DEFAULT
));
458 protected Composite
addDeleteAbility(final SwtSection section
, Content sessionNode
, int topWeight
,
460 Composite comp
= new Composite(section
, SWT
.NONE
);
461 comp
.setLayoutData(CmsSwtUtils
.fillAll());
462 comp
.setLayout(new FormLayout());
464 // The body to be populated
465 Composite body
= new Composite(comp
, SWT
.NO_FOCUS
);
466 body
.setLayoutData(EclipseUiUtils
.fillFormData());
468 if (getCmsEditable().canEdit()) {
470 Button deleteBtn
= new Button(comp
, SWT
.FLAT
);
471 CmsSwtUtils
.style(deleteBtn
, FormStyle
.deleteOverlay
.style());
472 FormData formData
= new FormData();
473 formData
.right
= new FormAttachment(rightWeight
, 0);
474 formData
.top
= new FormAttachment(topWeight
, 0);
475 deleteBtn
.setLayoutData(formData
);
476 deleteBtn
.moveAbove(body
);
478 deleteBtn
.addSelectionListener(new SelectionAdapter() {
479 private static final long serialVersionUID
= 4304223543657238462L;
482 public void widgetSelected(SelectionEvent e
) {
483 super.widgetSelected(e
);
484 // if (MessageDialog.openConfirm(section.getShell(), "Confirm deletion",
485 // "Are you really you want to remove this?")) {
488 // session = sessionNode.getSession();
489 // SwtSection parSection = section.getParentSection();
490 // sessionNode.remove();
492 // refresh(parSection);
493 // layout(parSection);
494 // } catch (RepositoryException re) {
495 // throw new JcrException("Unable to delete " + sessionNode, re);
506 // // LOCAL HELPERS FOR NODE MANAGEMENT
507 // private Node getOrCreateNode(Node parent, String nodeName, String nodeType) throws RepositoryException {
509 // if (getCmsEditable().canEdit() && !parent.hasNode(nodeName)) {
510 // node = JcrUtils.mkdirs(parent, nodeName, nodeType);
511 // parent.getSession().save();
514 // if (getCmsEditable().canEdit() || parent.hasNode(nodeName))
515 // node = parent.getNode(nodeName);
520 private SelectionListener
getRemoveValueSelListener() {
521 return new SelectionAdapter() {
522 private static final long serialVersionUID
= 9022259089907445195L;
525 public void widgetSelected(SelectionEvent e
) {
526 Object source
= e
.getSource();
527 if (source
instanceof Button
) {
528 Button btn
= (Button
) source
;
529 Object obj
= btn
.getData(FormConstants
.LINKED_VALUE
);
530 SwtEditablePart ep
= findDataParent(btn
);
531 if (ep
!= null && ep
instanceof EditableMultiStringProperty
) {
532 EditableMultiStringProperty emsp
= (EditableMultiStringProperty
) ep
;
533 List
<String
> values
= emsp
.getValues();
534 if (values
.contains(obj
)) {
535 values
.remove(values
.indexOf(obj
));
536 emsp
.setValues(values
);
538 // TODO workaround to force refresh
549 protected void setPropertySilently(Content node
, QName propName
, String value
) {
551 // Format strings to replace \n
552 value
= value
.replaceAll("\n", "<br/>");
553 // Do not make the update if validation fails
555 // MarkupValidatorCopy.getInstance().validate(value);
556 // } catch (Exception e) {
557 // log.warn("Cannot set [" + value + "] on prop " + propName + "of " + node
558 // + ", String cannot be validated - " + e.getMessage());
561 // TODO check if the newly created property is of the correct type,
562 // otherwise the property will be silently created with a STRING
564 node
.put(propName
, value
);