]> git.argeo.org Git - lgpl/argeo-commons.git/blob - FormPageViewer.java
7460a00fdd30bc854bdaef0cbaaa6b1d8f71f84e
[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.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 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 (FormUtils.notEmpty(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 try {
465 imageManager().uploadImage(context, name, stream);
466 // TODO clean refresh strategy
467 section.getDisplay().asyncExec(new Runnable() {
468 @Override
469 public void run() {
470 try {
471 FormPageViewer.this.refresh(section);
472 section.layout();
473 section.getParent().layout();
474 } catch (RepositoryException re) {
475 throw new ArgeoException("unable to refresh "
476 + "image section for " + context);
477 }
478 }
479 });
480 } catch (RepositoryException re) {
481 throw new ArgeoException("unable to upload image " + name
482 + " at " + context);
483 }
484 }
485 }
486
487 protected void addListeners(StyledControl control) {
488 control.setMouseListener(getMouseListener());
489 control.setFocusListener(getFocusListener());
490 }
491
492 protected Img createImgComposite(Composite parent, Node node,
493 Point preferredSize) throws RepositoryException {
494 Img img = new Img(parent, SWT.NONE, node, preferredSize) {
495 private static final long serialVersionUID = 1297900641952417540L;
496
497 @Override
498 protected void setContainerLayoutData(Composite composite) {
499 composite.setLayoutData(CmsUtils.grabWidth(SWT.CENTER,
500 SWT.DEFAULT));
501 }
502
503 @Override
504 protected void setControlLayoutData(Control control) {
505 control.setLayoutData(CmsUtils.grabWidth(SWT.CENTER,
506 SWT.DEFAULT));
507 }
508 };
509 img.setLayoutData(CmsUtils.grabWidth(SWT.CENTER, SWT.DEFAULT));
510 updateContent(img);
511 addListeners(img);
512 return img;
513 }
514
515 protected Composite addDeleteAbility(final Section section,
516 final Node sessionNode, int topWeight, int rightWeight) {
517 Composite comp = new Composite(section, SWT.NONE);
518 comp.setLayoutData(CmsUtils.fillAll());
519 comp.setLayout(new FormLayout());
520
521 // The body to be populated
522 Composite body = new Composite(comp, SWT.NO_FOCUS);
523 body.setLayoutData(EclipseUiUtils.fillFormData());
524
525 if (getCmsEditable().canEdit()) {
526 // the delete button
527 Button deleteBtn = new Button(comp, SWT.FLAT);
528 CmsUtils.style(deleteBtn, FormStyle.deleteOverlay.style());
529 FormData formData = new FormData();
530 formData.right = new FormAttachment(rightWeight, 0);
531 formData.top = new FormAttachment(topWeight, 0);
532 deleteBtn.setLayoutData(formData);
533 deleteBtn.moveAbove(body);
534
535 deleteBtn.addSelectionListener(new SelectionAdapter() {
536 private static final long serialVersionUID = 4304223543657238462L;
537
538 @Override
539 public void widgetSelected(SelectionEvent e) {
540 super.widgetSelected(e);
541 if (MessageDialog.openConfirm(section.getShell(),
542 "Confirm deletion",
543 "Are you really you want to remove this?")) {
544 Session session;
545 try {
546 session = sessionNode.getSession();
547 Section parSection = section.getParentSection();
548 sessionNode.remove();
549 session.save();
550 refresh(parSection);
551 layout(parSection);
552 } catch (RepositoryException re) {
553 throw new ArgeoException("Unable to delete "
554 + sessionNode, re);
555 }
556
557 }
558
559 }
560 });
561 }
562 return body;
563 }
564
565 // LOCAL HELPERS FOR NODE MANAGEMENT
566 protected Node getOrCreateNode(Node parent, String nodeType, String nodeName)
567 throws RepositoryException {
568 Node node = null;
569 if (getCmsEditable().canEdit() && !parent.hasNode(nodeName)) {
570 node = JcrUtils.mkdirs(parent, nodeName, nodeType);
571 parent.getSession().save();
572 }
573
574 if (getCmsEditable().canEdit() || parent.hasNode(nodeName))
575 node = parent.getNode(nodeName);
576
577 return node;
578 }
579
580 private SelectionListener getRemoveValueSelListener() {
581 return new SelectionAdapter() {
582 private static final long serialVersionUID = 9022259089907445195L;
583
584 @Override
585 public void widgetSelected(SelectionEvent e) {
586 Object source = e.getSource();
587 if (source instanceof Button) {
588 Button btn = (Button) source;
589 Object obj = btn.getData(FormConstants.LINKED_VALUE);
590 EditablePart ep = findDataParent(btn);
591 if (ep != null && ep instanceof EditableMultiStringProperty) {
592 EditableMultiStringProperty emsp = (EditableMultiStringProperty) ep;
593 List<String> values = emsp.getValues();
594 if (values.contains(obj)) {
595 values.remove(values.indexOf(obj));
596 emsp.setValues(values);
597 try {
598 save(emsp);
599 // TODO workaround to force refresh
600 edit(emsp, 0);
601 cancelEdit();
602 } catch (RepositoryException e1) {
603 throw new ArgeoException(
604 "Unable to remove value " + obj, e1);
605 }
606 layout(emsp);
607 }
608 }
609 }
610 }
611 };
612 }
613
614 protected void setPropertySilently(Node node, String propName, String value)
615 throws RepositoryException {
616 try {
617 // TODO Clean this:
618 // Format strings to replace \n
619 value = value.replaceAll("\n", "<br/>");
620 // Do not make the update if validation fails
621 try {
622 MarkupValidatorCopy.getInstance().validate(value);
623 } catch (Exception e) {
624 log.warn("Cannot set [" + value + "] on prop " + propName
625 + "of " + node + ", String cannot be validated - "
626 + e.getMessage());
627 return;
628 }
629 // TODO check if the newly created property is of the correct type,
630 // otherwise the property will be silently created with a STRING
631 // property type.
632 node.setProperty(propName, value);
633 } catch (ValueFormatException vfe) {
634 log.warn("Cannot set [" + value + "] on prop " + propName + "of "
635 + node + " - " + vfe.getMessage());
636 }
637 }
638 }