]> git.argeo.org Git - lgpl/argeo-commons.git/blob - FormPageViewer.java
8526fcaca11148b31df485bed075e618fe51691b
[lgpl/argeo-commons.git] / FormPageViewer.java
1 package org.argeo.cms.forms;
2
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;
9 import java.util.List;
10
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;
16
17 import org.apache.commons.logging.Log;
18 import org.apache.commons.logging.LogFactory;
19 import org.argeo.cms.CmsEditable;
20 import org.argeo.cms.CmsException;
21 import org.argeo.cms.CmsImageManager;
22 import org.argeo.cms.CmsNames;
23 import org.argeo.cms.text.Img;
24 import org.argeo.cms.ui.internal.text.MarkupValidatorCopy;
25 import org.argeo.cms.util.CmsUtils;
26 import org.argeo.cms.viewers.AbstractPageViewer;
27 import org.argeo.cms.viewers.EditablePart;
28 import org.argeo.cms.viewers.Section;
29 import org.argeo.cms.viewers.SectionPart;
30 import org.argeo.cms.widgets.EditableImage;
31 import org.argeo.cms.widgets.StyledControl;
32 import org.argeo.eclipse.ui.EclipseUiUtils;
33 import org.argeo.jcr.JcrUtils;
34 import org.eclipse.jface.dialogs.MessageDialog;
35 import org.eclipse.rap.fileupload.FileDetails;
36 import org.eclipse.rap.fileupload.FileUploadEvent;
37 import org.eclipse.rap.fileupload.FileUploadHandler;
38 import org.eclipse.rap.fileupload.FileUploadListener;
39 import org.eclipse.rap.fileupload.FileUploadReceiver;
40 import org.eclipse.rap.rwt.service.ServerPushSession;
41 import org.eclipse.rap.rwt.widgets.FileUpload;
42 import org.eclipse.swt.SWT;
43 import org.eclipse.swt.events.FocusEvent;
44 import org.eclipse.swt.events.FocusListener;
45 import org.eclipse.swt.events.MouseAdapter;
46 import org.eclipse.swt.events.MouseEvent;
47 import org.eclipse.swt.events.MouseListener;
48 import org.eclipse.swt.events.SelectionAdapter;
49 import org.eclipse.swt.events.SelectionEvent;
50 import org.eclipse.swt.events.SelectionListener;
51 import org.eclipse.swt.graphics.Point;
52 import org.eclipse.swt.layout.FormAttachment;
53 import org.eclipse.swt.layout.FormData;
54 import org.eclipse.swt.layout.FormLayout;
55 import org.eclipse.swt.layout.GridData;
56 import org.eclipse.swt.layout.GridLayout;
57 import org.eclipse.swt.layout.RowLayout;
58 import org.eclipse.swt.widgets.Button;
59 import org.eclipse.swt.widgets.Composite;
60 import org.eclipse.swt.widgets.Control;
61 import org.eclipse.swt.widgets.Label;
62 import org.eclipse.swt.widgets.Text;
63
64 /** Manage life cycle of a form page that is linked to a given node */
65 public class FormPageViewer extends AbstractPageViewer {
66 private final static Log log = LogFactory.getLog(FormPageViewer.class);
67 private static final long serialVersionUID = 5277789504209413500L;
68
69 private final Section mainSection;
70
71 // TODO manage within the CSS
72 private int labelColWidth = 150;
73 private int rowLayoutHSpacing = 8;
74
75 // Context cached in the viewer
76 // The reference to translate from text to calendar and reverse
77 private DateFormat dateFormat = new SimpleDateFormat(
78 FormUtils.DEFAULT_SHORT_DATE_FORMAT);
79 private CmsImageManager imageManager;
80 private FileUploadListener fileUploadListener;
81
82 public FormPageViewer(Section mainSection, int style,
83 CmsEditable cmsEditable) throws RepositoryException {
84 super(mainSection, style, cmsEditable);
85 this.mainSection = mainSection;
86
87 if (getCmsEditable().canEdit()) {
88 fileUploadListener = new FUL();
89 }
90 }
91
92 @Override
93 protected void prepare(EditablePart part, Object caretPosition) {
94 if (part instanceof Img) {
95 ((Img) part).setFileUploadListener(fileUploadListener);
96 }
97 }
98
99 /** To be overridden.Save the edited part. */
100 protected void save(EditablePart part) throws RepositoryException {
101 Node node = null;
102 if (part instanceof EditableMultiStringProperty) {
103 EditableMultiStringProperty ept = (EditableMultiStringProperty) part;
104 // SWT : View
105 List<String> values = ept.getValues();
106 // JCR : Model
107 node = ept.getNode();
108 String propName = ept.getPropertyName();
109 if (values.isEmpty()) {
110 if (node.hasProperty(propName))
111 node.getProperty(propName).remove();
112 } else {
113 node.setProperty(propName, values.toArray(new String[0]));
114 }
115 // => Viewer : Controller
116 } else if (part instanceof EditablePropertyString) {
117 EditablePropertyString ept = (EditablePropertyString) part;
118 // SWT : View
119 String txt = ((Text) ept.getControl()).getText();
120 // JCR : Model
121 node = ept.getNode();
122 String propName = ept.getPropertyName();
123 if (EclipseUiUtils.isEmpty(txt)) {
124 if (node.hasProperty(propName))
125 node.getProperty(propName).remove();
126 } else {
127 setPropertySilently(node, propName, txt);
128 // node.setProperty(propName, txt);
129 }
130 // node.getSession().save();
131 // => Viewer : Controller
132 } else if (part instanceof EditablePropertyDate) {
133 EditablePropertyDate ept = (EditablePropertyDate) part;
134 Calendar cal = FormUtils.parseDate(dateFormat,
135 ((Text) ept.getControl()).getText());
136 node = ept.getNode();
137 String propName = ept.getPropertyName();
138 if (cal == null) {
139 if (node.hasProperty(propName))
140 node.getProperty(propName).remove();
141 } else {
142 node.setProperty(propName, cal);
143 }
144 // node.getSession().save();
145 // => Viewer : Controller
146 }
147 // TODO: make this configurable, sometimes we do not want to save the
148 // current session at this stage
149 if (node != null && node.getSession().hasPendingChanges()) {
150 JcrUtils.updateLastModified(node);
151 node.getSession().save();
152 }
153 }
154
155 @Override
156 protected void updateContent(EditablePart part) throws RepositoryException {
157 if (part instanceof EditableMultiStringProperty) {
158 EditableMultiStringProperty ept = (EditableMultiStringProperty) part;
159 // SWT : View
160 Node node = ept.getNode();
161 String propName = ept.getPropertyName();
162 List<String> valStrings = new ArrayList<String>();
163 if (node.hasProperty(propName)) {
164 Value[] values = node.getProperty(propName).getValues();
165 for (Value val : values)
166 valStrings.add(val.getString());
167 }
168 ept.setValues(valStrings);
169 } else if (part instanceof EditablePropertyString) {
170 // || part instanceof EditableLink
171 EditablePropertyString ept = (EditablePropertyString) part;
172 // JCR : Model
173 Node node = ept.getNode();
174 String propName = ept.getPropertyName();
175 if (node.hasProperty(propName)) {
176 String value = node.getProperty(propName).getString();
177 ept.setText(value);
178 } else
179 ept.setText("");
180 // => Viewer : Controller
181 } else if (part instanceof EditablePropertyDate) {
182 EditablePropertyDate ept = (EditablePropertyDate) part;
183 // JCR : Model
184 Node node = ept.getNode();
185 String propName = ept.getPropertyName();
186 if (node.hasProperty(propName))
187 ept.setText(dateFormat.format(node.getProperty(propName)
188 .getDate().getTime()));
189 else
190 ept.setText("");
191 } else if (part instanceof SectionPart) {
192 SectionPart sectionPart = (SectionPart) part;
193 Node partNode = sectionPart.getNode();
194 // use control AFTER setting style, since it may have been reset
195 if (part instanceof EditableImage) {
196 EditableImage editableImage = (EditableImage) part;
197 imageManager().load(partNode, part.getControl(),
198 editableImage.getPreferredImageSize());
199 }
200 }
201 }
202
203 // FILE UPLOAD LISTENER
204 protected class FUL implements FileUploadListener {
205
206 public FUL() {
207 }
208
209 public void uploadProgress(FileUploadEvent event) {
210 // TODO Monitor upload progress
211 }
212
213 public void uploadFailed(FileUploadEvent event) {
214 throw new CmsException("Upload failed " + event,
215 event.getException());
216 }
217
218 public void uploadFinished(FileUploadEvent event) {
219 for (FileDetails file : event.getFileDetails()) {
220 if (log.isDebugEnabled())
221 log.debug("Received: " + file.getFileName());
222 }
223 mainSection.getDisplay().syncExec(new Runnable() {
224 @Override
225 public void run() {
226 saveEdit();
227 }
228 });
229 FileUploadHandler uploadHandler = (FileUploadHandler) event
230 .getSource();
231 uploadHandler.dispose();
232 }
233 }
234
235 // FOCUS OUT LISTENER
236 protected FocusListener createFocusListener() {
237 return new FocusOutListener();
238 }
239
240 private class FocusOutListener implements FocusListener {
241 private static final long serialVersionUID = -6069205786732354186L;
242
243 @Override
244 public void focusLost(FocusEvent event) {
245 saveEdit();
246 }
247
248 @Override
249 public void focusGained(FocusEvent event) {
250 // does nothing;
251 }
252 }
253
254 // MOUSE LISTENER
255 @Override
256 protected MouseListener createMouseListener() {
257 return new ML();
258 }
259
260 private class ML extends MouseAdapter {
261 private static final long serialVersionUID = 8526890859876770905L;
262
263 @Override
264 public void mouseDoubleClick(MouseEvent e) {
265 if (e.button == 1) {
266 Control source = (Control) e.getSource();
267 if (getCmsEditable().canEdit()) {
268 if (getCmsEditable().isEditing()
269 && !(getEdited() instanceof Img)) {
270 if (source == mainSection)
271 return;
272 EditablePart part = findDataParent(source);
273 upload(part);
274 } else {
275 getCmsEditable().startEditing();
276 }
277 }
278 }
279 }
280
281 @Override
282 public void mouseDown(MouseEvent e) {
283 if (getCmsEditable().isEditing()) {
284 if (e.button == 1) {
285 Control source = (Control) e.getSource();
286 EditablePart composite = findDataParent(source);
287 Point point = new Point(e.x, e.y);
288 if (!(composite instanceof Img))
289 edit(composite, source.toDisplay(point));
290 } else if (e.button == 3) {
291 // EditablePart composite = findDataParent((Control) e
292 // .getSource());
293 // if (styledTools != null)
294 // styledTools.show(composite, new Point(e.x, e.y));
295 }
296 }
297 }
298
299 protected synchronized void upload(EditablePart part) {
300 if (part instanceof SectionPart) {
301 if (part instanceof Img) {
302 if (getEdited() == part)
303 return;
304 edit(part, null);
305 layout(part.getControl());
306 }
307 }
308 }
309 }
310
311 @Override
312 public Control getControl() {
313 return mainSection;
314 }
315
316 protected CmsImageManager imageManager() {
317 if (imageManager == null)
318 imageManager = CmsUtils.getCmsView().getImageManager();
319 return imageManager;
320 }
321
322 // LOCAL UI HELPERS
323 protected Section createSectionIfNeeded(Composite body, Node node)
324 throws RepositoryException {
325 Section section = null;
326 if (node != null) {
327 section = new Section(body, SWT.NO_FOCUS, node);
328 section.setLayoutData(CmsUtils.fillWidth());
329 section.setLayout(CmsUtils.noSpaceGridLayout());
330 }
331 return section;
332 }
333
334 protected void createSimpleLT(Composite bodyRow, Node node,
335 String propName, String label, String msg)
336 throws RepositoryException {
337 if (getCmsEditable().canEdit() || node.hasProperty(propName)) {
338 createPropertyLbl(bodyRow, label);
339 EditablePropertyString eps = new EditablePropertyString(bodyRow,
340 SWT.WRAP | SWT.LEFT, node, propName, msg);
341 eps.setMouseListener(getMouseListener());
342 eps.setFocusListener(getFocusListener());
343 eps.setLayoutData(CmsUtils.fillWidth());
344 }
345 }
346
347 protected void createMultiStringLT(Composite bodyRow, Node node,
348 String propName, String label, String msg)
349 throws RepositoryException {
350 boolean canEdit = getCmsEditable().canEdit();
351 if (canEdit || node.hasProperty(propName)) {
352 createPropertyLbl(bodyRow, label);
353
354 List<String> valueStrings = new ArrayList<String>();
355
356 if (node.hasProperty(propName)) {
357 Value[] values = node.getProperty(propName).getValues();
358 for (Value value : values)
359 valueStrings.add(value.getString());
360 }
361
362 // TODO use a drop down to display possible values to the end user
363 EditableMultiStringProperty emsp = new EditableMultiStringProperty(
364 bodyRow, SWT.SINGLE | SWT.LEAD, node, propName,
365 valueStrings, new String[] { "Implement this" }, msg,
366 canEdit ? getRemoveValueSelListener() : null);
367 addListeners(emsp);
368 // emsp.setMouseListener(getMouseListener());
369 emsp.setStyle(FormStyle.propertyMessage.style());
370 emsp.setLayoutData(CmsUtils.fillWidth());
371 }
372 }
373
374 protected Label createPropertyLbl(Composite parent, String value) {
375 return createPropertyLbl(parent, value, SWT.TOP);
376 }
377
378 protected Label createPropertyLbl(Composite parent, String value, int vAlign) {
379 boolean isSmall = CmsUtils.getCmsView().getUxContext().isSmall();
380 Label label = new Label(parent, isSmall ? SWT.LEFT : SWT.RIGHT
381 | SWT.WRAP);
382 label.setText(value + " ");
383 CmsUtils.style(label, FormStyle.propertyLabel.style());
384 GridData gd = new GridData(isSmall ? SWT.LEFT : SWT.RIGHT, vAlign,
385 false, false);
386 gd.widthHint = labelColWidth;
387 label.setLayoutData(gd);
388 return label;
389 }
390
391 protected Label newStyledLabel(Composite parent, String style, String value) {
392 Label label = new Label(parent, SWT.NONE);
393 label.setText(value);
394 CmsUtils.style(label, style);
395 return label;
396 }
397
398 protected Composite createRowLayoutComposite(Composite parent)
399 throws RepositoryException {
400 Composite bodyRow = new Composite(parent, SWT.NO_FOCUS);
401 bodyRow.setLayoutData(CmsUtils.fillWidth());
402 RowLayout rl = new RowLayout(SWT.WRAP);
403 rl.type = SWT.HORIZONTAL;
404 rl.spacing = rowLayoutHSpacing;
405 rl.marginHeight = rl.marginWidth = 0;
406 rl.marginTop = rl.marginBottom = rl.marginLeft = rl.marginRight = 0;
407 bodyRow.setLayout(rl);
408 return bodyRow;
409 }
410
411 protected Composite createAddImgComposite(final Section section,
412 Composite parent, final Node parentNode) throws RepositoryException {
413
414 Composite body = new Composite(parent, SWT.NO_FOCUS);
415 body.setLayout(new GridLayout());
416
417 FormFileUploadReceiver receiver = new FormFileUploadReceiver(section,
418 parentNode, null);
419 final FileUploadHandler currentUploadHandler = new FileUploadHandler(
420 receiver);
421 if (fileUploadListener != null)
422 currentUploadHandler.addUploadListener(fileUploadListener);
423
424 // Button creation
425 final FileUpload fileUpload = new FileUpload(body, SWT.BORDER);
426 fileUpload.setText("Import an image");
427 fileUpload.setLayoutData(new GridData(SWT.CENTER, SWT.CENTER, true,
428 true));
429 fileUpload.addSelectionListener(new SelectionAdapter() {
430 private static final long serialVersionUID = 4869523412991968759L;
431
432 @Override
433 public void widgetSelected(SelectionEvent e) {
434 ServerPushSession pushSession = new ServerPushSession();
435 pushSession.start();
436 String uploadURL = currentUploadHandler.getUploadUrl();
437 fileUpload.submit(uploadURL);
438 }
439 });
440
441 return body;
442 }
443
444 protected class FormFileUploadReceiver extends FileUploadReceiver implements
445 CmsNames {
446
447 private Node context;
448 private Section section;
449 private String name;
450
451 public FormFileUploadReceiver(Section section, Node context, String name) {
452 this.context = context;
453 this.section = section;
454 this.name = name;
455 }
456
457 @Override
458 public void receive(InputStream stream, FileDetails details)
459 throws IOException {
460
461 if (name == null)
462 name = details.getFileName();
463
464 // TODO clean image name more carefully
465 String cleanedName = name.replaceAll("[^a-zA-Z0-9-.]", "_");
466
467 try {
468 imageManager().uploadImage(context, cleanedName, stream);
469 // TODO clean refresh strategy
470 section.getDisplay().asyncExec(new Runnable() {
471 @Override
472 public void run() {
473 try {
474 FormPageViewer.this.refresh(section);
475 section.layout();
476 section.getParent().layout();
477 } catch (RepositoryException re) {
478 throw new CmsException("unable to refresh "
479 + "image section for " + context);
480 }
481 }
482 });
483 } catch (RepositoryException re) {
484 throw new CmsException("unable to upload image " + name
485 + " at " + context);
486 }
487 }
488 }
489
490 protected void addListeners(StyledControl control) {
491 control.setMouseListener(getMouseListener());
492 control.setFocusListener(getFocusListener());
493 }
494
495 protected Img createImgComposite(Composite parent, Node node,
496 Point preferredSize) throws RepositoryException {
497 Img img = new Img(parent, SWT.NONE, node, preferredSize) {
498 private static final long serialVersionUID = 1297900641952417540L;
499
500 @Override
501 protected void setContainerLayoutData(Composite composite) {
502 composite.setLayoutData(CmsUtils.grabWidth(SWT.CENTER,
503 SWT.DEFAULT));
504 }
505
506 @Override
507 protected void setControlLayoutData(Control control) {
508 control.setLayoutData(CmsUtils.grabWidth(SWT.CENTER,
509 SWT.DEFAULT));
510 }
511 };
512 img.setLayoutData(CmsUtils.grabWidth(SWT.CENTER, SWT.DEFAULT));
513 updateContent(img);
514 addListeners(img);
515 return img;
516 }
517
518 protected Composite addDeleteAbility(final Section section,
519 final Node sessionNode, int topWeight, int rightWeight) {
520 Composite comp = new Composite(section, SWT.NONE);
521 comp.setLayoutData(CmsUtils.fillAll());
522 comp.setLayout(new FormLayout());
523
524 // The body to be populated
525 Composite body = new Composite(comp, SWT.NO_FOCUS);
526 body.setLayoutData(EclipseUiUtils.fillFormData());
527
528 if (getCmsEditable().canEdit()) {
529 // the delete button
530 Button deleteBtn = new Button(comp, SWT.FLAT);
531 CmsUtils.style(deleteBtn, FormStyle.deleteOverlay.style());
532 FormData formData = new FormData();
533 formData.right = new FormAttachment(rightWeight, 0);
534 formData.top = new FormAttachment(topWeight, 0);
535 deleteBtn.setLayoutData(formData);
536 deleteBtn.moveAbove(body);
537
538 deleteBtn.addSelectionListener(new SelectionAdapter() {
539 private static final long serialVersionUID = 4304223543657238462L;
540
541 @Override
542 public void widgetSelected(SelectionEvent e) {
543 super.widgetSelected(e);
544 if (MessageDialog.openConfirm(section.getShell(),
545 "Confirm deletion",
546 "Are you really you want to remove this?")) {
547 Session session;
548 try {
549 session = sessionNode.getSession();
550 Section parSection = section.getParentSection();
551 sessionNode.remove();
552 session.save();
553 refresh(parSection);
554 layout(parSection);
555 } catch (RepositoryException re) {
556 throw new CmsException("Unable to delete "
557 + sessionNode, re);
558 }
559
560 }
561
562 }
563 });
564 }
565 return body;
566 }
567
568 // LOCAL HELPERS FOR NODE MANAGEMENT
569 protected Node getOrCreateNode(Node parent, String nodeType, String nodeName)
570 throws RepositoryException {
571 Node node = null;
572 if (getCmsEditable().canEdit() && !parent.hasNode(nodeName)) {
573 node = JcrUtils.mkdirs(parent, nodeName, nodeType);
574 parent.getSession().save();
575 }
576
577 if (getCmsEditable().canEdit() || parent.hasNode(nodeName))
578 node = parent.getNode(nodeName);
579
580 return node;
581 }
582
583 private SelectionListener getRemoveValueSelListener() {
584 return new SelectionAdapter() {
585 private static final long serialVersionUID = 9022259089907445195L;
586
587 @Override
588 public void widgetSelected(SelectionEvent e) {
589 Object source = e.getSource();
590 if (source instanceof Button) {
591 Button btn = (Button) source;
592 Object obj = btn.getData(FormConstants.LINKED_VALUE);
593 EditablePart ep = findDataParent(btn);
594 if (ep != null && ep instanceof EditableMultiStringProperty) {
595 EditableMultiStringProperty emsp = (EditableMultiStringProperty) ep;
596 List<String> values = emsp.getValues();
597 if (values.contains(obj)) {
598 values.remove(values.indexOf(obj));
599 emsp.setValues(values);
600 try {
601 save(emsp);
602 // TODO workaround to force refresh
603 edit(emsp, 0);
604 cancelEdit();
605 } catch (RepositoryException e1) {
606 throw new CmsException(
607 "Unable to remove value " + obj, e1);
608 }
609 layout(emsp);
610 }
611 }
612 }
613 }
614 };
615 }
616
617 protected void setPropertySilently(Node node, String propName, String value)
618 throws RepositoryException {
619 try {
620 // TODO Clean this:
621 // Format strings to replace \n
622 value = value.replaceAll("\n", "<br/>");
623 // Do not make the update if validation fails
624 try {
625 MarkupValidatorCopy.getInstance().validate(value);
626 } catch (Exception e) {
627 log.warn("Cannot set [" + value + "] on prop " + propName
628 + "of " + node + ", String cannot be validated - "
629 + e.getMessage());
630 return;
631 }
632 // TODO check if the newly created property is of the correct type,
633 // otherwise the property will be silently created with a STRING
634 // property type.
635 node.setProperty(propName, value);
636 } catch (ValueFormatException vfe) {
637 log.warn("Cannot set [" + value + "] on prop " + propName + "of "
638 + node + " - " + vfe.getMessage());
639 }
640 }
641 }