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