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