]> git.argeo.org Git - lgpl/argeo-commons.git/blob - forms/FormPageViewer.java
Prepare next development cycle
[lgpl/argeo-commons.git] / forms / 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.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;
64
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;
69
70 private final Section mainSection;
71
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;
79
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;
86
87 public FormPageViewer(Section mainSection, int style,
88 CmsEditable cmsEditable) throws RepositoryException {
89 super(mainSection, style, cmsEditable);
90 this.mainSection = mainSection;
91
92 if (getCmsEditable().canEdit()) {
93 fileUploadListener = new FUL();
94 }
95 }
96
97 @Override
98 protected void prepare(EditablePart part, Object caretPosition) {
99 if (part instanceof Img) {
100 ((Img) part).setFileUploadListener(fileUploadListener);
101 }
102 }
103
104 /** To be overridden.Save the edited part. */
105 protected void save(EditablePart part) throws RepositoryException {
106 Node node = null;
107 if (part instanceof EditableMultiStringProperty) {
108 EditableMultiStringProperty ept = (EditableMultiStringProperty) part;
109 // SWT : View
110 List<String> values = ept.getValues();
111 // JCR : Model
112 node = ept.getNode();
113 String propName = ept.getPropertyName();
114 if (values.isEmpty()) {
115 if (node.hasProperty(propName))
116 node.getProperty(propName).remove();
117 } else {
118 node.setProperty(propName, values.toArray(new String[0]));
119 }
120 // => Viewer : Controller
121 } else if (part instanceof EditablePropertyString) {
122 EditablePropertyString ept = (EditablePropertyString) part;
123 // SWT : View
124 String txt = ((Text) ept.getControl()).getText();
125 // JCR : Model
126 node = ept.getNode();
127 String propName = ept.getPropertyName();
128 if (FormUtils.notEmpty(txt)) {
129 if (node.hasProperty(propName))
130 node.getProperty(propName).remove();
131 } else {
132 setPropertySilently(node, propName, txt);
133 // node.setProperty(propName, txt);
134 }
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();
143 if (cal == null) {
144 if (node.hasProperty(propName))
145 node.getProperty(propName).remove();
146 } else {
147 node.setProperty(propName, cal);
148 }
149 // node.getSession().save();
150 // => Viewer : Controller
151 }
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();
157 }
158 }
159
160 @Override
161 protected void updateContent(EditablePart part) throws RepositoryException {
162 if (part instanceof EditableMultiStringProperty) {
163 EditableMultiStringProperty ept = (EditableMultiStringProperty) part;
164 // SWT : View
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());
172 }
173 ept.setValues(valStrings);
174 } else if (part instanceof EditablePropertyString) {
175 // || part instanceof EditableLink
176 EditablePropertyString ept = (EditablePropertyString) part;
177 // JCR : Model
178 Node node = ept.getNode();
179 String propName = ept.getPropertyName();
180 if (node.hasProperty(propName)) {
181 String value = node.getProperty(propName).getString();
182 ept.setText(value);
183 } else
184 ept.setText("");
185 // => Viewer : Controller
186 } else if (part instanceof EditablePropertyDate) {
187 EditablePropertyDate ept = (EditablePropertyDate) part;
188 // JCR : Model
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()));
194 else
195 ept.setText("");
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());
204 }
205 }
206 }
207
208 // FILE UPLOAD LISTENER
209 protected class FUL implements FileUploadListener {
210
211 public FUL() {
212 }
213
214 public void uploadProgress(FileUploadEvent event) {
215 // TODO Monitor upload progress
216 }
217
218 public void uploadFailed(FileUploadEvent event) {
219 throw new CmsException("Upload failed " + event,
220 event.getException());
221 }
222
223 public void uploadFinished(FileUploadEvent event) {
224 for (FileDetails file : event.getFileDetails()) {
225 if (log.isDebugEnabled())
226 log.debug("Received: " + file.getFileName());
227 }
228 mainSection.getDisplay().syncExec(new Runnable() {
229 @Override
230 public void run() {
231 saveEdit();
232 }
233 });
234 FileUploadHandler uploadHandler = (FileUploadHandler) event
235 .getSource();
236 uploadHandler.dispose();
237 }
238 }
239
240 // FOCUS OUT LISTENER
241 protected FocusListener createFocusListener() {
242 return new FocusOutListener();
243 }
244
245 private class FocusOutListener implements FocusListener {
246 private static final long serialVersionUID = -6069205786732354186L;
247
248 @Override
249 public void focusLost(FocusEvent event) {
250 saveEdit();
251 }
252
253 @Override
254 public void focusGained(FocusEvent event) {
255 // does nothing;
256 }
257 }
258
259 // MOUSE LISTENER
260 @Override
261 protected MouseListener createMouseListener() {
262 return new ML();
263 }
264
265 private class ML extends MouseAdapter {
266 private static final long serialVersionUID = 8526890859876770905L;
267
268 @Override
269 public void mouseDoubleClick(MouseEvent e) {
270 if (e.button == 1) {
271 Control source = (Control) e.getSource();
272 if (getCmsEditable().canEdit()) {
273 if (getCmsEditable().isEditing()
274 && !(getEdited() instanceof Img)) {
275 if (source == mainSection)
276 return;
277 EditablePart part = findDataParent(source);
278 upload(part);
279 } else {
280 getCmsEditable().startEditing();
281 }
282 }
283 }
284 }
285
286 @Override
287 public void mouseDown(MouseEvent e) {
288 if (getCmsEditable().isEditing()) {
289 if (e.button == 1) {
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
297 // .getSource());
298 // if (styledTools != null)
299 // styledTools.show(composite, new Point(e.x, e.y));
300 }
301 }
302 }
303
304 protected synchronized void upload(EditablePart part) {
305 if (part instanceof SectionPart) {
306 if (part instanceof Img) {
307 if (getEdited() == part)
308 return;
309 edit(part, null);
310 layout(part.getControl());
311 }
312 }
313 }
314 }
315
316 @Override
317 public Control getControl() {
318 return mainSection;
319 }
320
321 protected CmsImageManager imageManager() {
322 if (imageManager == null)
323 imageManager = CmsUtils.getCmsView().getImageManager();
324 return imageManager;
325 }
326
327 // LOCAL UI HELPERS
328 protected Section createSectionIfNeeded(Composite body, Node node)
329 throws RepositoryException {
330 Section section = null;
331 if (node != null) {
332 section = new Section(body, SWT.NO_FOCUS, node);
333 section.setLayoutData(CmsUtils.fillWidth());
334 section.setLayout(CmsUtils.noSpaceGridLayout());
335 }
336 return section;
337 }
338
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());
349 }
350 }
351
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);
358
359 List<String> valueStrings = new ArrayList<String>();
360
361 if (node.hasProperty(propName)) {
362 Value[] values = node.getProperty(propName).getValues();
363 for (Value value : values)
364 valueStrings.add(value.getString());
365 }
366
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);
372 addListeners(emsp);
373 // emsp.setMouseListener(getMouseListener());
374 emsp.setStyle(FormStyle.propertyMessage.style());
375 emsp.setLayoutData(CmsUtils.fillWidth());
376 }
377 }
378
379 protected Label createPropertyLbl(Composite parent, String value) {
380 return createPropertyLbl(parent, value, SWT.TOP);
381 }
382
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);
390 return label;
391 }
392
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);
397 return label;
398 }
399
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);
410 return bodyRow;
411 }
412
413 protected Composite createSectionBody(Composite parent, int nbOfCol) {
414 // The separator line. Ugly workaround that should be better managed via
415 // css
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;
420 gd.heightHint = 0;
421 header.setLayoutData(gd);
422
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());
432
433 return bodyRow;
434 }
435
436 protected Composite createAddImgComposite(final Section section,
437 Composite parent, final Node parentNode) throws RepositoryException {
438
439 Composite body = new Composite(parent, SWT.NO_FOCUS);
440 body.setLayout(new GridLayout());
441
442 FormFileUploadReceiver receiver = new FormFileUploadReceiver(section,
443 parentNode, null);
444 final FileUploadHandler currentUploadHandler = new FileUploadHandler(
445 receiver);
446 if (fileUploadListener != null)
447 currentUploadHandler.addUploadListener(fileUploadListener);
448
449 // Button creation
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,
453 true));
454 fileUpload.addSelectionListener(new SelectionAdapter() {
455 private static final long serialVersionUID = 4869523412991968759L;
456
457 @Override
458 public void widgetSelected(SelectionEvent e) {
459 ServerPushSession pushSession = new ServerPushSession();
460 pushSession.start();
461 String uploadURL = currentUploadHandler.getUploadUrl();
462 fileUpload.submit(uploadURL);
463 }
464 });
465
466 return body;
467 }
468
469 protected class FormFileUploadReceiver extends FileUploadReceiver implements
470 CmsNames {
471
472 private Node context;
473 private Section section;
474 private String name;
475
476 public FormFileUploadReceiver(Section section, Node context, String name) {
477 this.context = context;
478 this.section = section;
479 this.name = name;
480 }
481
482 @Override
483 public void receive(InputStream stream, FileDetails details)
484 throws IOException {
485
486 if (name == null)
487 name = details.getFileName();
488 try {
489 imageManager().uploadImage(context, name, stream);
490 // TODO clean refresh strategy
491 section.getDisplay().asyncExec(new Runnable() {
492 @Override
493 public void run() {
494 try {
495 FormPageViewer.this.refresh(section);
496 section.layout();
497 section.getParent().layout();
498 } catch (RepositoryException re) {
499 throw new ArgeoException("unable to refresh "
500 + "image section for " + context);
501 }
502 }
503 });
504 } catch (RepositoryException re) {
505 throw new ArgeoException("unable to upload image " + name
506 + " at " + context);
507 }
508 }
509 }
510
511 protected void addListeners(StyledControl control) {
512 control.setMouseListener(getMouseListener());
513 control.setFocusListener(getFocusListener());
514 }
515
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;
520
521 @Override
522 protected void setContainerLayoutData(Composite composite) {
523 composite.setLayoutData(CmsUtils.grabWidth(SWT.CENTER,
524 SWT.DEFAULT));
525 }
526
527 @Override
528 protected void setControlLayoutData(Control control) {
529 control.setLayoutData(CmsUtils.grabWidth(SWT.CENTER,
530 SWT.DEFAULT));
531 }
532 };
533 img.setLayoutData(CmsUtils.grabWidth(SWT.CENTER, SWT.DEFAULT));
534 updateContent(img);
535 addListeners(img);
536 return img;
537 }
538
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());
544
545 // The body to be populated
546 Composite body = new Composite(comp, SWT.NO_FOCUS);
547 body.setLayoutData(EclipseUiUtils.fillFormData());
548
549 if (getCmsEditable().canEdit()) {
550 // the delete button
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);
558
559 deleteBtn.addSelectionListener(new SelectionAdapter() {
560 private static final long serialVersionUID = 4304223543657238462L;
561
562 @Override
563 public void widgetSelected(SelectionEvent e) {
564 super.widgetSelected(e);
565 if (MessageDialog.openConfirm(section.getShell(),
566 "Confirm deletion",
567 "Are you really you want to remove this?")) {
568 Session session;
569 try {
570 session = sessionNode.getSession();
571 Section parSection = section.getParentSection();
572 sessionNode.remove();
573 session.save();
574 refresh(parSection);
575 layout(parSection);
576 } catch (RepositoryException re) {
577 throw new ArgeoException("Unable to delete "
578 + sessionNode, re);
579 }
580
581 }
582
583 }
584 });
585 }
586 return body;
587 }
588
589 // LOCAL HELPERS FOR NODE MANAGEMENT
590 protected Node getOrCreateNode(Node parent, String nodeType, String nodeName)
591 throws RepositoryException {
592 Node node = null;
593 if (getCmsEditable().canEdit() && !parent.hasNode(nodeName)) {
594 node = JcrUtils.mkdirs(parent, nodeName, nodeType);
595 parent.getSession().save();
596 }
597
598 if (getCmsEditable().canEdit() || parent.hasNode(nodeName))
599 node = parent.getNode(nodeName);
600
601 return node;
602 }
603
604 private SelectionListener getRemoveValueSelListener() {
605 return new SelectionAdapter() {
606 private static final long serialVersionUID = 9022259089907445195L;
607
608 @Override
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);
621 try {
622 save(emsp);
623 // TODO workaround to force refresh
624 edit(emsp, 0);
625 cancelEdit();
626 } catch (RepositoryException e1) {
627 throw new ArgeoException(
628 "Unable to remove value " + obj, e1);
629 }
630 layout(emsp);
631 }
632 }
633 }
634 }
635 };
636 }
637
638 protected void setPropertySilently(Node node, String propName, String value)
639 throws RepositoryException {
640 try {
641 // TODO Clean this:
642 // Format strings to replace \n
643 value = value.replaceAll("\n", "<br/>");
644 // Do not make the update if validation fails
645 try {
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 - "
650 + e.getMessage());
651 return;
652 }
653 // TODO check if the newly created property is of the correct type,
654 // otherwise the property will be silently created with a STRING
655 // property type.
656 node.setProperty(propName, value);
657 } catch (ValueFormatException vfe) {
658 log.warn("Cannot set [" + value + "] on prop " + propName + "of "
659 + node + " - " + vfe.getMessage());
660 }
661 }
662 }