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