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