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