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