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