]> git.argeo.org Git - lgpl/argeo-commons.git/blob - org.argeo.cms/src/org/argeo/cms/maintenance/Browse.java
c6dd755f0f8c3b0531310160eecd4f7e03932e8d
[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 Object[][] colMatrix = new Object[browserCols.size()][2];
261
262 int i = 0, j = -1, k = -1;
263 for (String path : browserCols.keySet()) {
264 colMatrix[i][0] = path;
265 colMatrix[i][1] = browserCols.get(path);
266 if (j >= 0 && k < 0 && !currNodePath.equals("/")) {
267 boolean leaveOpened = path.startsWith(currNodePath);
268
269 // workaround for same name siblings
270 // fix me weird side effect when we go left or click on anb
271 // already selected, unfocused node
272 if (leaveOpened
273 && (path.lastIndexOf("/") == 0
274 && currNodePath.lastIndexOf("/") == 0 || JcrUtils
275 .parentPath(path).equals(
276 JcrUtils.parentPath(currNodePath))))
277 leaveOpened = JcrUtils.lastPathElement(path).equals(
278 JcrUtils.lastPathElement(currNodePath));
279
280 if (!leaveOpened)
281 k = i;
282 }
283 if (currParPath.equals(path))
284 j = i;
285 i++;
286 }
287
288 if (j >= 0 && k >= 0)
289 // remove useless cols
290 for (int l = i - 1; l >= k; l--) {
291 browserCols.remove(colMatrix[l][0]);
292 ((FilterEntitiesVirtualTable) colMatrix[l][1]).dispose();
293 }
294
295 if (!browserCols.containsKey(currNodePath))
296 createBrowserColumn(colViewer, node);
297
298 colViewer.setLayout(CmsUtils.noSpaceGridLayout(new GridLayout(
299 browserCols.size(), false)));
300 // colViewer.pack();
301 colViewer.layout();
302 // also resize the scrolled composite
303 scrolledCmp.layout();
304 scrolledCmp.getShowFocusedControl();
305 // colViewer.getParent().layout();
306 // if (JcrUtils.parentPath(currNodePath).equals(currBrowserKey)) {
307 // } else {
308 // }
309 }
310
311 private void modifyFilter(boolean fromOutside) {
312 if (!fromOutside)
313 if (currEdited != null) {
314 String filter = filterTxt.getText() + "*";
315 FilterEntitiesVirtualTable table = browserCols
316 .get(getPath(currEdited));
317 if (table != null && !table.isDisposed())
318 table.filterList(filter);
319 }
320
321 }
322
323 private String getPath(Node node) {
324 try {
325 return node.getPath();
326 } catch (RepositoryException e) {
327 throw new ArgeoException("Unable to get path for node " + node, e);
328 }
329 }
330
331 private Point imageWidth = new Point(250, 0);
332
333 private Control createNodeView(Composite parent, Node context)
334 throws RepositoryException {
335
336 parent.setLayout(new GridLayout(2, false));
337
338 if (isImg(context)) {
339 EditableImage image = new Img(parent, RIGHT, context, imageWidth);
340 image.setLayoutData(new GridData(SWT.CENTER, SWT.CENTER, true,
341 false, 2, 1));
342 }
343
344 // Name and primary type
345 Label contextL = new Label(parent, SWT.NONE);
346 contextL.setData(RWT.MARKUP_ENABLED, true);
347 contextL.setText("<b>" + context.getName() + "</b>");
348 new Label(parent, SWT.NONE).setText(context.getPrimaryNodeType()
349 .getName());
350
351 // Children
352 for (NodeIterator nIt = context.getNodes(); nIt.hasNext();) {
353 Node child = nIt.nextNode();
354 new CmsLink(child.getName(), BROWSE_PREFIX + child.getPath())
355 .createUi(parent, context);
356 new Label(parent, SWT.NONE).setText(child.getPrimaryNodeType()
357 .getName());
358 }
359
360 // Properties
361 for (PropertyIterator pIt = context.getProperties(); pIt.hasNext();) {
362 Property property = pIt.nextProperty();
363 Label label = new Label(parent, SWT.NONE);
364 label.setText(property.getName());
365 label.setToolTipText(JcrUtils
366 .getPropertyDefinitionAsString(property));
367 new Label(parent, SWT.NONE).setText(getPropAsString(property));
368 }
369
370 return null;
371 }
372
373 private boolean isImg(Node node) throws RepositoryException {
374 return node.hasNode(JCR_CONTENT) && node.isNodeType(CmsTypes.CMS_IMAGE);
375 }
376
377 private String getPropAsString(Property property)
378 throws RepositoryException {
379 String result = "";
380 if (property.isMultiple()) {
381 result = getMultiAsString(property, ", ");
382 } else {
383 Value value = property.getValue();
384 if (value.getType() == PropertyType.BINARY)
385 result = "<binary>";
386 else if (value.getType() == PropertyType.DATE)
387 result = timeFormatter.format(value.getDate().getTime());
388 else
389 result = value.getString();
390 }
391 return result;
392 }
393
394 private String getMultiAsString(Property property, String separator)
395 throws RepositoryException {
396 if (separator == null)
397 separator = "; ";
398 Value[] values = property.getValues();
399 StringBuilder builder = new StringBuilder();
400 for (Value val : values) {
401 String currStr = val.getString();
402 if (!"".equals(currStr.trim()))
403 builder.append(currStr).append(separator);
404 }
405 if (builder.lastIndexOf(separator) >= 0)
406 return builder.substring(0, builder.length() - separator.length());
407 else
408 return builder.toString();
409 }
410
411 /** Almost canonical implementation of a table that display entities */
412 private class FilterEntitiesVirtualTable extends Composite {
413 private static final long serialVersionUID = 8798147431706283824L;
414
415 // Context
416 private Node context;
417
418 // UI Objects
419 private TableViewer entityViewer;
420
421 // enable management of multiple columns
422 Node getNode() {
423 return context;
424 }
425
426 @Override
427 public boolean setFocus() {
428 if (entityViewer.getTable().isDisposed())
429 return false;
430 if (entityViewer.getSelection().isEmpty()) {
431 Object first = entityViewer.getElementAt(0);
432 if (first != null) {
433 entityViewer.setSelection(new StructuredSelection(first),
434 true);
435 }
436 }
437 return entityViewer.getTable().setFocus();
438 }
439
440 void filterList(String filter) {
441 try {
442 NodeIterator nit = context.getNodes(filter);
443 refreshFilteredList(nit);
444 } catch (RepositoryException e) {
445 throw new ArgeoException("Unable to filter " + getNode()
446 + " children with filter " + filter, e);
447 }
448
449 }
450
451 public FilterEntitiesVirtualTable(Composite parent, int style,
452 Node context) {
453 super(parent, SWT.NO_FOCUS);
454 this.context = context;
455 populate();
456 }
457
458 protected void populate() {
459 Composite parent = this;
460 GridLayout layout = CmsUtils.noSpaceGridLayout();
461
462 this.setLayout(layout);
463 createTableViewer(parent);
464 }
465
466 private void createTableViewer(final Composite parent) {
467 // the list
468 // We must limit the size of the table otherwise the full list is
469 // loaded
470 // before the layout happens
471 Composite listCmp = new Composite(parent, SWT.NO_FOCUS);
472 GridData gd = new GridData(SWT.LEFT, SWT.FILL, false, true);
473 gd.widthHint = COLUMN_WIDTH;
474 listCmp.setLayoutData(gd);
475 listCmp.setLayout(CmsUtils.noSpaceGridLayout());
476
477 entityViewer = new TableViewer(listCmp, SWT.VIRTUAL | SWT.SINGLE);
478 Table table = entityViewer.getTable();
479
480 table.setLayoutData(CmsUtils.fillAll());
481 table.setLinesVisible(true);
482 table.setHeaderVisible(false);
483 table.setData(RWT.MARKUP_ENABLED, Boolean.TRUE);
484
485 CmsUtils.style(table, MaintenanceStyles.BROWSER_COLUMN);
486
487 // first column
488 TableViewerColumn column = new TableViewerColumn(entityViewer,
489 SWT.NONE);
490 TableColumn tcol = column.getColumn();
491 tcol.setWidth(COLUMN_WIDTH);
492 tcol.setResizable(true);
493 column.setLabelProvider(new SimpleNameLP());
494
495 entityViewer.setContentProvider(new MyLazyCP(entityViewer));
496 entityViewer
497 .addSelectionChangedListener(new ISelectionChangedListener() {
498
499 @Override
500 public void selectionChanged(SelectionChangedEvent event) {
501 IStructuredSelection selection = (IStructuredSelection) entityViewer
502 .getSelection();
503 if (selection.isEmpty())
504 return;
505 else
506 setEdited((Node) selection.getFirstElement());
507
508 }
509 });
510
511 table.addKeyListener(new KeyListener() {
512 private static final long serialVersionUID = -330694313896036230L;
513
514 @Override
515 public void keyReleased(KeyEvent e) {
516 }
517
518 @Override
519 public void keyPressed(KeyEvent e) {
520
521 IStructuredSelection selection = (IStructuredSelection) entityViewer
522 .getSelection();
523 Node selected = null;
524 if (!selection.isEmpty())
525 selected = ((Node) selection.getFirstElement());
526 try {
527 if (e.keyCode == SWT.ARROW_RIGHT) {
528 if (selected != null) {
529 setEdited(selected);
530 browserCols.get(selected.getPath()).setFocus();
531 }
532 } else if (e.keyCode == SWT.ARROW_LEFT) {
533 try {
534 selected = getNode().getParent();
535 String newPath = selected.getPath(); // getNode().getParent()
536 setEdited(selected);
537 if (browserCols.containsKey(newPath))
538 browserCols.get(newPath).setFocus();
539 } catch (ItemNotFoundException ie) {
540 // root silent
541 }
542 }
543 } catch (RepositoryException ie) {
544 throw new ArgeoException("Error while managing arrow "
545 + "events in the browser for " + selected, ie);
546 }
547 }
548 });
549 }
550
551 private class MyLazyCP implements ILazyContentProvider {
552 private static final long serialVersionUID = 1L;
553 private TableViewer viewer;
554 private Object[] elements;
555
556 public MyLazyCP(TableViewer viewer) {
557 this.viewer = viewer;
558 }
559
560 public void dispose() {
561 }
562
563 public void inputChanged(Viewer viewer, Object oldInput,
564 Object newInput) {
565 // IMPORTANT: don't forget this: an exception will be thrown if
566 // a selected object is not part of the results anymore.
567 viewer.setSelection(null);
568 this.elements = (Object[]) newInput;
569 }
570
571 public void updateElement(int index) {
572 viewer.replace(elements[index], index);
573 }
574 }
575
576 protected void refreshFilteredList(NodeIterator children) {
577 Object[] rows = JcrUtils.nodeIteratorToList(children).toArray();
578 entityViewer.setInput(rows);
579 entityViewer.setItemCount(rows.length);
580 entityViewer.refresh();
581 }
582
583 public class SimpleNameLP extends ColumnLabelProvider {
584 private static final long serialVersionUID = 2465059387875338553L;
585
586 @Override
587 public String getText(Object element) {
588 if (element instanceof Node) {
589 Node curr = ((Node) element);
590 try {
591 return curr.getName();
592 } catch (RepositoryException e) {
593 throw new ArgeoException("Unable to get name for"
594 + curr);
595 }
596 }
597 return super.getText(element);
598 }
599 }
600 }
601 }