]> git.argeo.org Git - gpl/argeo-suite.git/blob - swt/org.argeo.app.swt/src/org/argeo/app/swt/forms/FormPageViewer.java
Remove legacy map overview
[gpl/argeo-suite.git] / swt / org.argeo.app.swt / src / org / argeo / app / swt / forms / FormPageViewer.java
1 package org.argeo.app.swt.forms;
2
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;
9 import java.util.List;
10
11 import javax.xml.namespace.QName;
12
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;
54
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);
58
59 private final SwtSection mainSection;
60
61 // TODO manage within the CSS
62 private Integer labelColWidth = null;
63 private int rowLayoutHSpacing = 8;
64
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;
71
72 public FormPageViewer(SwtSection mainSection, int style, CmsEditable cmsEditable) {
73 super(mainSection, style, cmsEditable);
74 this.mainSection = mainSection;
75
76 if (getCmsEditable().canEdit()) {
77 fileUploadListener = new FUL();
78 }
79 }
80
81 @Override
82 protected void prepare(SwtEditablePart part, Object caretPosition) {
83 if (part instanceof Img) {
84 // ((Img) part).setFileUploadListener(fileUploadListener);
85 }
86 }
87
88 /** To be overridden.Save the edited part. */
89 protected void save(SwtEditablePart part) {
90 Content node = null;
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);
98 } else {
99 node.put(propName, values);
100 // node.setProperty(propName, values.toArray(new String[0]));
101 }
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);
109 } else {
110 setPropertySilently(node, propName, txt);
111 // node.setProperty(propName, txt);
112 }
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();
121 if (cal == null) {
122 node.remove(propName);
123 } else {
124 node.put(propName, cal);
125 }
126 // node.getSession().save();
127 // => Viewer : Controller
128 }
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();
134 // }
135 }
136
137 @Override
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))
145 valStrings.add(val);
146 }
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()));
158 else
159 ept.setText("");
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);
166 }
167 }
168 }
169
170 // FILE UPLOAD LISTENER
171 protected class FUL implements FileUploadListener {
172
173 public FUL() {
174 }
175
176 public void uploadProgress(FileUploadEvent event) {
177 // TODO Monitor upload progress
178 }
179
180 public void uploadFailed(FileUploadEvent event) {
181 throw new IllegalStateException("Upload failed " + event, event.getException());
182 }
183
184 public void uploadFinished(FileUploadEvent event) {
185 for (FileDetails file : event.getFileDetails()) {
186 if (log.isDebugEnabled())
187 log.debug("Received: " + file.getFileName());
188 }
189 mainSection.getDisplay().syncExec(new Runnable() {
190 @Override
191 public void run() {
192 saveEdit();
193 }
194 });
195 FileUploadHandler uploadHandler = (FileUploadHandler) event.getSource();
196 uploadHandler.dispose();
197 }
198 }
199
200 // FOCUS OUT LISTENER
201 protected FocusListener createFocusListener() {
202 return new FocusOutListener();
203 }
204
205 private class FocusOutListener implements FocusListener {
206 private static final long serialVersionUID = -6069205786732354186L;
207
208 @Override
209 public void focusLost(FocusEvent event) {
210 saveEdit();
211 }
212
213 @Override
214 public void focusGained(FocusEvent event) {
215 // does nothing;
216 }
217 }
218
219 // MOUSE LISTENER
220 @Override
221 protected MouseListener createMouseListener() {
222 return new ML();
223 }
224
225 private class ML extends MouseAdapter {
226 private static final long serialVersionUID = 8526890859876770905L;
227
228 @Override
229 public void mouseDoubleClick(MouseEvent e) {
230 if (e.button == 1) {
231 Control source = (Control) e.getSource();
232 if (getCmsEditable().canEdit()) {
233 if (getCmsEditable().isEditing() && !(getEdited() instanceof Img)) {
234 if (source == mainSection)
235 return;
236 SwtEditablePart part = findDataParent(source);
237 upload(part);
238 } else {
239 getCmsEditable().startEditing();
240 }
241 }
242 }
243 }
244
245 @Override
246 public void mouseDown(MouseEvent e) {
247 if (getCmsEditable().isEditing()) {
248 if (e.button == 1) {
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
256 // .getSource());
257 // if (styledTools != null)
258 // styledTools.show(composite, new Point(e.x, e.y));
259 }
260 }
261 }
262
263 protected synchronized void upload(SwtEditablePart part) {
264 if (part instanceof SwtSectionPart) {
265 if (part instanceof Img) {
266 if (getEdited() == part)
267 return;
268 edit(part, null);
269 layout(part.getControl());
270 }
271 }
272 }
273 }
274
275 @Override
276 public Control getControl() {
277 return mainSection;
278 }
279
280 protected CmsImageManager<Control, Content> imageManager() {
281 if (imageManager == null)
282 imageManager = CmsSwtUtils.getCmsView(mainSection).getImageManager();
283 return imageManager;
284 }
285
286 // LOCAL UI HELPERS
287 protected SwtSection createSectionIfNeeded(Composite body, Content node) {
288 SwtSection section = null;
289 if (node != null) {
290 section = new SwtSection(body, SWT.NO_FOCUS, node);
291 section.setLayoutData(CmsSwtUtils.fillWidth());
292 section.setLayout(CmsSwtUtils.noSpaceGridLayout());
293 }
294 return section;
295 }
296
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());
304 }
305 }
306
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);
311
312 List<String> valueStrings = new ArrayList<String>();
313
314 if (node.containsKey(propName)) {
315 for (String value : node.getMultiple(propName, String.class))
316 valueStrings.add(value);
317 }
318
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);
323 addListeners(emsp);
324 // emsp.setMouseListener(getMouseListener());
325 emsp.setStyle(FormStyle.propertyMessage.style());
326 emsp.setLayoutData(CmsSwtUtils.fillWidth());
327 }
328 }
329
330 protected Label createPropertyLbl(Composite parent, String value) {
331 return createPropertyLbl(parent, value, SWT.NONE);
332 }
333
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);
343 return label;
344 }
345
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);
350 return label;
351 }
352
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);
362 return bodyRow;
363 }
364
365 protected Composite createAddImgComposite(SwtSection section, Composite parent, Content parentNode) {
366
367 Composite body = new Composite(parent, SWT.NO_FOCUS);
368 body.setLayout(new GridLayout());
369
370 FormFileUploadReceiver receiver = new FormFileUploadReceiver(section, parentNode, null);
371 final FileUploadHandler currentUploadHandler = new FileUploadHandler(receiver);
372 if (fileUploadListener != null)
373 currentUploadHandler.addUploadListener(fileUploadListener);
374
375 // Button creation
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;
381
382 @Override
383 public void widgetSelected(SelectionEvent e) {
384 ServerPushSession pushSession = new ServerPushSession();
385 pushSession.start();
386 String uploadURL = currentUploadHandler.getUploadUrl();
387 fileUpload.submit(uploadURL);
388 }
389 });
390
391 return body;
392 }
393
394 protected class FormFileUploadReceiver extends FileUploadReceiver {
395
396 private Content context;
397 private SwtSection section;
398 private String name;
399
400 public FormFileUploadReceiver(SwtSection section, Content context, String name) {
401 this.context = context;
402 this.section = section;
403 this.name = name;
404 }
405
406 @Override
407 public void receive(InputStream stream, FileDetails details) throws IOException {
408
409 if (name == null)
410 name = details.getFileName();
411
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
417 // for a while
418 cleanedName = System.currentTimeMillis() % 100000 + "_" + cleanedName;
419
420 imageManager().uploadImage(context, context, cleanedName, stream, details.getContentType());
421 // TODO clean refresh strategy
422 section.getDisplay().asyncExec(new Runnable() {
423 @Override
424 public void run() {
425 FormPageViewer.this.refresh(section);
426 section.layout();
427 section.getParent().layout();
428 }
429 });
430 }
431 }
432
433 protected void addListeners(StyledControl control) {
434 control.setMouseListener(getMouseListener());
435 control.setFocusListener(getFocusListener());
436 }
437
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;
441
442 @Override
443 protected void setContainerLayoutData(Composite composite) {
444 composite.setLayoutData(CmsSwtUtils.grabWidth(SWT.CENTER, SWT.DEFAULT));
445 }
446
447 @Override
448 protected void setControlLayoutData(Control control) {
449 control.setLayoutData(CmsSwtUtils.grabWidth(SWT.CENTER, SWT.DEFAULT));
450 }
451 };
452 img.setLayoutData(CmsSwtUtils.grabWidth(SWT.CENTER, SWT.DEFAULT));
453 updateContent(img);
454 addListeners(img);
455 return img;
456 }
457
458 protected Composite addDeleteAbility(final SwtSection section, Content sessionNode, int topWeight,
459 int rightWeight) {
460 Composite comp = new Composite(section, SWT.NONE);
461 comp.setLayoutData(CmsSwtUtils.fillAll());
462 comp.setLayout(new FormLayout());
463
464 // The body to be populated
465 Composite body = new Composite(comp, SWT.NO_FOCUS);
466 body.setLayoutData(EclipseUiUtils.fillFormData());
467
468 if (getCmsEditable().canEdit()) {
469 // the delete button
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);
477
478 deleteBtn.addSelectionListener(new SelectionAdapter() {
479 private static final long serialVersionUID = 4304223543657238462L;
480
481 @Override
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?")) {
486 // Session session;
487 // try {
488 // session = sessionNode.getSession();
489 // SwtSection parSection = section.getParentSection();
490 // sessionNode.remove();
491 // session.save();
492 // refresh(parSection);
493 // layout(parSection);
494 // } catch (RepositoryException re) {
495 // throw new JcrException("Unable to delete " + sessionNode, re);
496 // }
497 //
498 // }
499
500 }
501 });
502 }
503 return body;
504 }
505
506 // // LOCAL HELPERS FOR NODE MANAGEMENT
507 // private Node getOrCreateNode(Node parent, String nodeName, String nodeType) throws RepositoryException {
508 // Node node = null;
509 // if (getCmsEditable().canEdit() && !parent.hasNode(nodeName)) {
510 // node = JcrUtils.mkdirs(parent, nodeName, nodeType);
511 // parent.getSession().save();
512 // }
513 //
514 // if (getCmsEditable().canEdit() || parent.hasNode(nodeName))
515 // node = parent.getNode(nodeName);
516 //
517 // return node;
518 // }
519
520 private SelectionListener getRemoveValueSelListener() {
521 return new SelectionAdapter() {
522 private static final long serialVersionUID = 9022259089907445195L;
523
524 @Override
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);
537 save(emsp);
538 // TODO workaround to force refresh
539 edit(emsp, 0);
540 cancelEdit();
541 layout(emsp);
542 }
543 }
544 }
545 }
546 };
547 }
548
549 protected void setPropertySilently(Content node, QName propName, String value) {
550 // TODO Clean this:
551 // Format strings to replace \n
552 value = value.replaceAll("\n", "<br/>");
553 // Do not make the update if validation fails
554 // try {
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());
559 // return;
560 // }
561 // TODO check if the newly created property is of the correct type,
562 // otherwise the property will be silently created with a STRING
563 // property type.
564 node.put(propName, value);
565 }
566 }