]> git.argeo.org Git - lgpl/argeo-commons.git/blob - org.argeo.cms.e4/src/org/argeo/cms/e4/maintenance/Browse.java
Expose servlet context helpers.
[lgpl/argeo-commons.git] / org.argeo.cms.e4 / src / org / argeo / cms / e4 / maintenance / Browse.java
1 package org.argeo.cms.e4.maintenance;
2
3 import static org.eclipse.swt.SWT.RIGHT;
4
5 import java.text.DateFormat;
6 import java.text.SimpleDateFormat;
7 import java.util.LinkedHashMap;
8
9 import javax.jcr.ItemNotFoundException;
10 import javax.jcr.Node;
11 import javax.jcr.NodeIterator;
12 import javax.jcr.Property;
13 import javax.jcr.PropertyIterator;
14 import javax.jcr.PropertyType;
15 import javax.jcr.RepositoryException;
16 import javax.jcr.Value;
17
18 import org.argeo.cms.CmsException;
19 import org.argeo.cms.ui.CmsUiProvider;
20 import org.argeo.cms.ui.util.CmsLink;
21 import org.argeo.cms.ui.util.CmsUiUtils;
22 import org.argeo.cms.ui.widgets.EditableImage;
23 import org.argeo.cms.ui.widgets.Img;
24 import org.argeo.jcr.JcrUtils;
25 import org.eclipse.jface.viewers.ColumnLabelProvider;
26 import org.eclipse.jface.viewers.ILazyContentProvider;
27 import org.eclipse.jface.viewers.ISelectionChangedListener;
28 import org.eclipse.jface.viewers.IStructuredSelection;
29 import org.eclipse.jface.viewers.SelectionChangedEvent;
30 import org.eclipse.jface.viewers.StructuredSelection;
31 import org.eclipse.jface.viewers.TableViewer;
32 import org.eclipse.jface.viewers.TableViewerColumn;
33 import org.eclipse.jface.viewers.Viewer;
34 import org.eclipse.swt.SWT;
35 import org.eclipse.swt.custom.ScrolledComposite;
36 import org.eclipse.swt.events.ControlAdapter;
37 import org.eclipse.swt.events.ControlEvent;
38 import org.eclipse.swt.events.KeyEvent;
39 import org.eclipse.swt.events.KeyListener;
40 import org.eclipse.swt.events.ModifyEvent;
41 import org.eclipse.swt.events.ModifyListener;
42 import org.eclipse.swt.graphics.Point;
43 import org.eclipse.swt.graphics.Rectangle;
44 import org.eclipse.swt.layout.GridData;
45 import org.eclipse.swt.layout.GridLayout;
46 import org.eclipse.swt.widgets.Composite;
47 import org.eclipse.swt.widgets.Control;
48 import org.eclipse.swt.widgets.Label;
49 import org.eclipse.swt.widgets.Table;
50 import org.eclipse.swt.widgets.TableColumn;
51 import org.eclipse.swt.widgets.Text;
52
53 public class Browse implements CmsUiProvider {
54
55 // Some local constants to experiment. should be cleaned
56 private final static String BROWSE_PREFIX = "browse#";
57 private final static int THUMBNAIL_WIDTH = 400;
58 private final static int COLUMN_WIDTH = 160;
59 private DateFormat timeFormatter = new SimpleDateFormat("dd-MM-yyyy', 'HH:mm");
60
61 // keep a cache of the opened nodes
62 // Key is the path
63 private LinkedHashMap<String, FilterEntitiesVirtualTable> browserCols = new LinkedHashMap<String, Browse.FilterEntitiesVirtualTable>();
64 private Composite nodeDisplayParent;
65 private Composite colViewer;
66 private ScrolledComposite scrolledCmp;
67 private Text parentPathTxt;
68 private Text filterTxt;
69 private Node currEdited;
70
71 private String initialPath;
72
73 @Override
74 public Control createUi(Composite parent, Node context) throws RepositoryException {
75 if (context == null)
76 // return null;
77 throw new CmsException("Context cannot be null");
78 GridLayout layout = CmsUiUtils.noSpaceGridLayout();
79 layout.numColumns = 2;
80 parent.setLayout(layout);
81
82 // Left
83 Composite leftCmp = new Composite(parent, SWT.NO_FOCUS);
84 leftCmp.setLayoutData(CmsUiUtils.fillAll());
85 createBrowserPart(leftCmp, context);
86
87 // Right
88 nodeDisplayParent = new Composite(parent, SWT.NO_FOCUS | SWT.BORDER);
89 GridData gd = new GridData(SWT.RIGHT, SWT.FILL, false, true);
90 gd.widthHint = THUMBNAIL_WIDTH;
91 nodeDisplayParent.setLayoutData(gd);
92 createNodeView(nodeDisplayParent, context);
93
94 // INIT
95 setEdited(context);
96 initialPath = context.getPath();
97
98 // Workaround we don't yet manage the delete to display parent of the
99 // initial context node
100
101 return null;
102 }
103
104 private void createBrowserPart(Composite parent, Node context) throws RepositoryException {
105 GridLayout layout = CmsUiUtils.noSpaceGridLayout();
106 parent.setLayout(layout);
107 Composite filterCmp = new Composite(parent, SWT.NO_FOCUS);
108 filterCmp.setLayoutData(CmsUiUtils.fillWidth());
109
110 // top filter
111 addFilterPanel(filterCmp);
112
113 // scrolled composite
114 scrolledCmp = new ScrolledComposite(parent, SWT.H_SCROLL | SWT.BORDER | SWT.NO_FOCUS);
115 scrolledCmp.setLayoutData(CmsUiUtils.fillAll());
116 scrolledCmp.setExpandVertical(true);
117 scrolledCmp.setExpandHorizontal(true);
118 scrolledCmp.setShowFocusedControl(true);
119
120 colViewer = new Composite(scrolledCmp, SWT.NO_FOCUS);
121 scrolledCmp.setContent(colViewer);
122 scrolledCmp.addControlListener(new ControlAdapter() {
123 private static final long serialVersionUID = 6589392045145698201L;
124
125 @Override
126 public void controlResized(ControlEvent e) {
127 Rectangle r = scrolledCmp.getClientArea();
128 scrolledCmp.setMinSize(colViewer.computeSize(SWT.DEFAULT, r.height));
129 }
130 });
131 initExplorer(colViewer, context);
132 }
133
134 private Control initExplorer(Composite parent, Node context) throws RepositoryException {
135 parent.setLayout(CmsUiUtils.noSpaceGridLayout());
136 createBrowserColumn(parent, context);
137 return null;
138 }
139
140 private Control createBrowserColumn(Composite parent, Node context) throws RepositoryException {
141 // TODO style is not correctly managed.
142 FilterEntitiesVirtualTable table = new FilterEntitiesVirtualTable(parent, SWT.BORDER | SWT.NO_FOCUS, context);
143 // CmsUiUtils.style(table, ArgeoOrgStyle.browserColumn.style());
144 table.filterList("*");
145 table.setLayoutData(new GridData(SWT.LEFT, SWT.FILL, false, true));
146 browserCols.put(context.getPath(), table);
147 return null;
148 }
149
150 public void addFilterPanel(Composite parent) {
151
152 parent.setLayout(CmsUiUtils.noSpaceGridLayout(new GridLayout(2, false)));
153
154 // Text Area for the filter
155 parentPathTxt = new Text(parent, SWT.NO_FOCUS);
156 parentPathTxt.setEditable(false);
157 filterTxt = new Text(parent, SWT.SEARCH | SWT.ICON_CANCEL);
158 filterTxt.setMessage("Filter current list");
159 filterTxt.setLayoutData(CmsUiUtils.fillWidth());
160 filterTxt.addModifyListener(new ModifyListener() {
161 private static final long serialVersionUID = 7709303319740056286L;
162
163 public void modifyText(ModifyEvent event) {
164 modifyFilter(false);
165 }
166 });
167
168 filterTxt.addKeyListener(new KeyListener() {
169 private static final long serialVersionUID = -4523394262771183968L;
170
171 @Override
172 public void keyReleased(KeyEvent e) {
173 }
174
175 @Override
176 public void keyPressed(KeyEvent e) {
177 boolean shiftPressed = (e.stateMask & SWT.SHIFT) != 0;
178 // boolean altPressed = (e.stateMask & SWT.ALT) != 0;
179 FilterEntitiesVirtualTable currTable = null;
180 if (currEdited != null) {
181 FilterEntitiesVirtualTable table = browserCols.get(getPath(currEdited));
182 if (table != null && !table.isDisposed())
183 currTable = table;
184 }
185
186 try {
187 if (e.keyCode == SWT.ARROW_DOWN)
188 currTable.setFocus();
189 else if (e.keyCode == SWT.BS) {
190 if (filterTxt.getText().equals("")
191 && !(getPath(currEdited).equals("/") || getPath(currEdited).equals(initialPath))) {
192 setEdited(currEdited.getParent());
193 e.doit = false;
194 filterTxt.setFocus();
195 }
196 } else if (e.keyCode == SWT.TAB && !shiftPressed) {
197 if (currEdited.getNodes(filterTxt.getText() + "*").getSize() == 1) {
198 setEdited(currEdited.getNodes(filterTxt.getText() + "*").nextNode());
199 }
200 filterTxt.setFocus();
201 e.doit = false;
202 }
203 } catch (RepositoryException e1) {
204 throw new CmsException("Unexpected error in key management for " + currEdited + "with filter "
205 + filterTxt.getText(), e1);
206 }
207
208 }
209 });
210 }
211
212 private void setEdited(Node node) {
213 try {
214 currEdited = node;
215 CmsUiUtils.clear(nodeDisplayParent);
216 createNodeView(nodeDisplayParent, currEdited);
217 nodeDisplayParent.layout();
218 refreshFilters(node);
219 refreshBrowser(node);
220 } catch (RepositoryException re) {
221 throw new CmsException("Unable to update browser for " + node, re);
222 }
223 }
224
225 private void refreshFilters(Node node) throws RepositoryException {
226 String currNodePath = node.getPath();
227 parentPathTxt.setText(currNodePath);
228 filterTxt.setText("");
229 filterTxt.getParent().layout();
230 }
231
232 private void refreshBrowser(Node node) throws RepositoryException {
233
234 // Retrieve
235 String currNodePath = node.getPath();
236 String currParPath = "";
237 if (!"/".equals(currNodePath))
238 currParPath = JcrUtils.parentPath(currNodePath);
239 if ("".equals(currParPath))
240 currParPath = "/";
241
242 Object[][] colMatrix = new Object[browserCols.size()][2];
243
244 int i = 0, j = -1, k = -1;
245 for (String path : browserCols.keySet()) {
246 colMatrix[i][0] = path;
247 colMatrix[i][1] = browserCols.get(path);
248 if (j >= 0 && k < 0 && !currNodePath.equals("/")) {
249 boolean leaveOpened = path.startsWith(currNodePath);
250
251 // workaround for same name siblings
252 // fix me weird side effect when we go left or click on anb
253 // already selected, unfocused node
254 if (leaveOpened && (path.lastIndexOf("/") == 0 && currNodePath.lastIndexOf("/") == 0
255 || JcrUtils.parentPath(path).equals(JcrUtils.parentPath(currNodePath))))
256 leaveOpened = JcrUtils.lastPathElement(path).equals(JcrUtils.lastPathElement(currNodePath));
257
258 if (!leaveOpened)
259 k = i;
260 }
261 if (currParPath.equals(path))
262 j = i;
263 i++;
264 }
265
266 if (j >= 0 && k >= 0)
267 // remove useless cols
268 for (int l = i - 1; l >= k; l--) {
269 browserCols.remove(colMatrix[l][0]);
270 ((FilterEntitiesVirtualTable) colMatrix[l][1]).dispose();
271 }
272
273 // Remove disposed columns
274 // TODO investigate and fix the mechanism that leave them there after
275 // disposal
276 if (browserCols.containsKey(currNodePath)) {
277 FilterEntitiesVirtualTable currCol = browserCols.get(currNodePath);
278 if (currCol.isDisposed())
279 browserCols.remove(currNodePath);
280 }
281
282 if (!browserCols.containsKey(currNodePath))
283 createBrowserColumn(colViewer, node);
284
285 colViewer.setLayout(CmsUiUtils.noSpaceGridLayout(new GridLayout(browserCols.size(), false)));
286 // colViewer.pack();
287 colViewer.layout();
288 // also resize the scrolled composite
289 scrolledCmp.layout();
290 scrolledCmp.getShowFocusedControl();
291 // colViewer.getParent().layout();
292 // if (JcrUtils.parentPath(currNodePath).equals(currBrowserKey)) {
293 // } else {
294 // }
295 }
296
297 private void modifyFilter(boolean fromOutside) {
298 if (!fromOutside)
299 if (currEdited != null) {
300 String filter = filterTxt.getText() + "*";
301 FilterEntitiesVirtualTable table = browserCols.get(getPath(currEdited));
302 if (table != null && !table.isDisposed())
303 table.filterList(filter);
304 }
305
306 }
307
308 private String getPath(Node node) {
309 try {
310 return node.getPath();
311 } catch (RepositoryException e) {
312 throw new CmsException("Unable to get path for node " + node, e);
313 }
314 }
315
316 private Point imageWidth = new Point(250, 0);
317
318 /**
319 * Recreates the content of the box that displays information about the current
320 * selected node.
321 */
322 private Control createNodeView(Composite parent, Node context) throws RepositoryException {
323
324 parent.setLayout(new GridLayout(2, false));
325
326 if (isImg(context)) {
327 EditableImage image = new Img(parent, RIGHT, context, imageWidth);
328 image.setLayoutData(new GridData(SWT.CENTER, SWT.CENTER, true, false, 2, 1));
329 }
330
331 // Name and primary type
332 Label contextL = new Label(parent, SWT.NONE);
333 CmsUiUtils.markup(contextL);
334 contextL.setText("<b>" + context.getName() + "</b>");
335 new Label(parent, SWT.NONE).setText(context.getPrimaryNodeType().getName());
336
337 // Children
338 for (NodeIterator nIt = context.getNodes(); nIt.hasNext();) {
339 Node child = nIt.nextNode();
340 new CmsLink(child.getName(), BROWSE_PREFIX + child.getPath()).createUi(parent, context);
341 new Label(parent, SWT.NONE).setText(child.getPrimaryNodeType().getName());
342 }
343
344 // Properties
345 for (PropertyIterator pIt = context.getProperties(); pIt.hasNext();) {
346 Property property = pIt.nextProperty();
347 Label label = new Label(parent, SWT.NONE);
348 label.setText(property.getName());
349 label.setToolTipText(JcrUtils.getPropertyDefinitionAsString(property));
350 new Label(parent, SWT.NONE).setText(getPropAsString(property));
351 }
352
353 return null;
354 }
355
356 private boolean isImg(Node node) throws RepositoryException {
357 // TODO support images
358 return false;
359 // return node.hasNode(JCR_CONTENT) && node.isNodeType(CmsTypes.CMS_IMAGE);
360 }
361
362 private String getPropAsString(Property property) throws RepositoryException {
363 String result = "";
364 if (property.isMultiple()) {
365 result = getMultiAsString(property, ", ");
366 } else {
367 Value value = property.getValue();
368 if (value.getType() == PropertyType.BINARY)
369 result = "<binary>";
370 else if (value.getType() == PropertyType.DATE)
371 result = timeFormatter.format(value.getDate().getTime());
372 else
373 result = value.getString();
374 }
375 return result;
376 }
377
378 private String getMultiAsString(Property property, String separator) throws RepositoryException {
379 if (separator == null)
380 separator = "; ";
381 Value[] values = property.getValues();
382 StringBuilder builder = new StringBuilder();
383 for (Value val : values) {
384 String currStr = val.getString();
385 if (!"".equals(currStr.trim()))
386 builder.append(currStr).append(separator);
387 }
388 if (builder.lastIndexOf(separator) >= 0)
389 return builder.substring(0, builder.length() - separator.length());
390 else
391 return builder.toString();
392 }
393
394 /** Almost canonical implementation of a table that display entities */
395 private class FilterEntitiesVirtualTable extends Composite {
396 private static final long serialVersionUID = 8798147431706283824L;
397
398 // Context
399 private Node context;
400
401 // UI Objects
402 private TableViewer entityViewer;
403
404 // enable management of multiple columns
405 Node getNode() {
406 return context;
407 }
408
409 @Override
410 public boolean setFocus() {
411 if (entityViewer.getTable().isDisposed())
412 return false;
413 if (entityViewer.getSelection().isEmpty()) {
414 Object first = entityViewer.getElementAt(0);
415 if (first != null) {
416 entityViewer.setSelection(new StructuredSelection(first), true);
417 }
418 }
419 return entityViewer.getTable().setFocus();
420 }
421
422 void filterList(String filter) {
423 try {
424 NodeIterator nit = context.getNodes(filter);
425 refreshFilteredList(nit);
426 } catch (RepositoryException e) {
427 throw new CmsException("Unable to filter " + getNode() + " children with filter " + filter, e);
428 }
429
430 }
431
432 public FilterEntitiesVirtualTable(Composite parent, int style, Node context) {
433 super(parent, SWT.NO_FOCUS);
434 this.context = context;
435 populate();
436 }
437
438 protected void populate() {
439 Composite parent = this;
440 GridLayout layout = CmsUiUtils.noSpaceGridLayout();
441
442 this.setLayout(layout);
443 createTableViewer(parent);
444 }
445
446 private void createTableViewer(final Composite parent) {
447 // the list
448 // We must limit the size of the table otherwise the full list is
449 // loaded
450 // before the layout happens
451 Composite listCmp = new Composite(parent, SWT.NO_FOCUS);
452 GridData gd = new GridData(SWT.LEFT, SWT.FILL, false, true);
453 gd.widthHint = COLUMN_WIDTH;
454 listCmp.setLayoutData(gd);
455 listCmp.setLayout(CmsUiUtils.noSpaceGridLayout());
456
457 entityViewer = new TableViewer(listCmp, SWT.VIRTUAL | SWT.SINGLE);
458 Table table = entityViewer.getTable();
459
460 table.setLayoutData(CmsUiUtils.fillAll());
461 table.setLinesVisible(true);
462 table.setHeaderVisible(false);
463 CmsUiUtils.markup(table);
464
465 CmsUiUtils.style(table, MaintenanceStyles.BROWSER_COLUMN);
466
467 // first column
468 TableViewerColumn column = new TableViewerColumn(entityViewer, SWT.NONE);
469 TableColumn tcol = column.getColumn();
470 tcol.setWidth(COLUMN_WIDTH);
471 tcol.setResizable(true);
472 column.setLabelProvider(new SimpleNameLP());
473
474 entityViewer.setContentProvider(new MyLazyCP(entityViewer));
475 entityViewer.addSelectionChangedListener(new ISelectionChangedListener() {
476
477 @Override
478 public void selectionChanged(SelectionChangedEvent event) {
479 IStructuredSelection selection = (IStructuredSelection) entityViewer.getSelection();
480 if (selection.isEmpty())
481 return;
482 else
483 setEdited((Node) selection.getFirstElement());
484
485 }
486 });
487
488 table.addKeyListener(new KeyListener() {
489 private static final long serialVersionUID = -330694313896036230L;
490
491 @Override
492 public void keyReleased(KeyEvent e) {
493 }
494
495 @Override
496 public void keyPressed(KeyEvent e) {
497
498 IStructuredSelection selection = (IStructuredSelection) entityViewer.getSelection();
499 Node selected = null;
500 if (!selection.isEmpty())
501 selected = ((Node) selection.getFirstElement());
502 try {
503 if (e.keyCode == SWT.ARROW_RIGHT) {
504 if (selected != null) {
505 setEdited(selected);
506 browserCols.get(selected.getPath()).setFocus();
507 }
508 } else if (e.keyCode == SWT.ARROW_LEFT) {
509 try {
510 selected = getNode().getParent();
511 String newPath = selected.getPath(); // getNode().getParent()
512 setEdited(selected);
513 if (browserCols.containsKey(newPath))
514 browserCols.get(newPath).setFocus();
515 } catch (ItemNotFoundException ie) {
516 // root silent
517 }
518 }
519 } catch (RepositoryException ie) {
520 throw new CmsException("Error while managing arrow " + "events in the browser for " + selected,
521 ie);
522 }
523 }
524 });
525 }
526
527 private class MyLazyCP implements ILazyContentProvider {
528 private static final long serialVersionUID = 1L;
529 private TableViewer viewer;
530 private Object[] elements;
531
532 public MyLazyCP(TableViewer viewer) {
533 this.viewer = viewer;
534 }
535
536 public void dispose() {
537 }
538
539 public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
540 // IMPORTANT: don't forget this: an exception will be thrown if
541 // a selected object is not part of the results anymore.
542 viewer.setSelection(null);
543 this.elements = (Object[]) newInput;
544 }
545
546 public void updateElement(int index) {
547 viewer.replace(elements[index], index);
548 }
549 }
550
551 protected void refreshFilteredList(NodeIterator children) {
552 Object[] rows = JcrUtils.nodeIteratorToList(children).toArray();
553 entityViewer.setInput(rows);
554 entityViewer.setItemCount(rows.length);
555 entityViewer.refresh();
556 }
557
558 public class SimpleNameLP extends ColumnLabelProvider {
559 private static final long serialVersionUID = 2465059387875338553L;
560
561 @Override
562 public String getText(Object element) {
563 if (element instanceof Node) {
564 Node curr = ((Node) element);
565 try {
566 return curr.getName();
567 } catch (RepositoryException e) {
568 throw new CmsException("Unable to get name for" + curr);
569 }
570 }
571 return super.getText(element);
572 }
573 }
574 }
575 }