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