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