1 package org
.argeo
.cms
.maintenance
;
3 import static javax
.jcr
.Node
.JCR_CONTENT
;
4 import static org
.eclipse
.swt
.SWT
.RIGHT
;
6 import java
.text
.DateFormat
;
7 import java
.text
.SimpleDateFormat
;
8 import java
.util
.LinkedHashMap
;
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
;
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
;
57 public class Browse
implements CmsUiProvider
{
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");
66 // keep a cache of the opened nodes
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
;
76 private String initialPath
;
79 public Control
createUi(Composite parent
, Node context
)
80 throws RepositoryException
{
83 throw new CmsException("Context cannot be null");
84 GridLayout layout
= CmsUtils
.noSpaceGridLayout();
85 layout
.numColumns
= 2;
86 parent
.setLayout(layout
);
89 Composite leftCmp
= new Composite(parent
, SWT
.NO_FOCUS
);
90 leftCmp
.setLayoutData(CmsUtils
.fillAll());
91 createBrowserPart(leftCmp
, context
);
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
);
102 initialPath
= context
.getPath();
104 // Workaround we don't yet manage the delete to display parent of the
105 // initial context node
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());
118 addFilterPanel(filterCmp
);
120 // scrolled composite
121 scrolledCmp
= new ScrolledComposite(parent
, SWT
.H_SCROLL
| SWT
.BORDER
123 scrolledCmp
.setLayoutData(CmsUtils
.fillAll());
124 scrolledCmp
.setExpandVertical(true);
125 scrolledCmp
.setExpandHorizontal(true);
126 scrolledCmp
.setShowFocusedControl(true);
128 colViewer
= new Composite(scrolledCmp
, SWT
.NO_FOCUS
);
129 scrolledCmp
.setContent(colViewer
);
130 scrolledCmp
.addControlListener(new ControlAdapter() {
131 private static final long serialVersionUID
= 6589392045145698201L;
134 public void controlResized(ControlEvent e
) {
135 Rectangle r
= scrolledCmp
.getClientArea();
136 scrolledCmp
.setMinSize(colViewer
.computeSize(SWT
.DEFAULT
,
140 initExplorer(colViewer
, context
);
143 private Control
initExplorer(Composite parent
, Node context
)
144 throws RepositoryException
{
145 parent
.setLayout(CmsUtils
.noSpaceGridLayout());
146 createBrowserColumn(parent
, context
);
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
);
162 public void addFilterPanel(Composite parent
) {
164 parent
.setLayout(CmsUtils
.noSpaceGridLayout(new GridLayout(2, false)));
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;
175 public void modifyText(ModifyEvent event
) {
180 filterTxt
.addKeyListener(new KeyListener() {
181 private static final long serialVersionUID
= -4523394262771183968L;
184 public void keyReleased(KeyEvent e
) {
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())
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());
208 filterTxt
.setFocus();
210 } else if (e
.keyCode
== SWT
.TAB
&& !shiftPressed
) {
211 if (currEdited
.getNodes(filterTxt
.getText() + "*")
213 setEdited(currEdited
.getNodes(
214 filterTxt
.getText() + "*").nextNode());
216 filterTxt
.setFocus();
219 } catch (RepositoryException e1
) {
220 throw new ArgeoException(
221 "Unexpected error in key management for "
222 + currEdited
+ "with filter "
223 + filterTxt
.getText(), e1
);
230 private void setEdited(Node 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
);
243 private void refreshFilters(Node node
) throws RepositoryException
{
244 String currNodePath
= node
.getPath();
245 parentPathTxt
.setText(currNodePath
);
246 filterTxt
.setText("");
247 filterTxt
.getParent().layout();
250 private void refreshBrowser(Node node
) throws RepositoryException
{
253 String currNodePath
= node
.getPath();
254 String currParPath
= "";
255 if (!"/".equals(currNodePath
))
256 currParPath
= JcrUtils
.parentPath(currNodePath
);
257 if ("".equals(currParPath
))
263 Object
[][] colMatrix
= new Object
[browserCols
.size()][2];
265 int i
= 0, j
= -1, k
= -1;
266 for (String path
: browserCols
.keySet()) {
267 colMatrix
[i
][0] = path
;
268 colMatrix
[i
][1] = browserCols
.get(path
);
269 if (j
>= 0 && k
< 0 && !currNodePath
.equals("/")) {
270 boolean leaveOpened
= path
.startsWith(currNodePath
);
272 // workaround for same name siblings
273 // fix me weird side effect when we go left or click on anb
274 // already selected, unfocused node
276 && (path
.lastIndexOf("/") == 0
277 && currNodePath
.lastIndexOf("/") == 0 || JcrUtils
278 .parentPath(path
).equals(
279 JcrUtils
.parentPath(currNodePath
))))
280 leaveOpened
= JcrUtils
.lastPathElement(path
).equals(
281 JcrUtils
.lastPathElement(currNodePath
));
286 if (currParPath
.equals(path
))
291 if (j
>= 0 && k
>= 0)
292 // remove useless cols
293 for (int l
= i
- 1; l
>= k
; l
--) {
294 browserCols
.remove(colMatrix
[l
][0]);
295 ((FilterEntitiesVirtualTable
) colMatrix
[l
][1]).dispose();
298 // Remove disposed columns
299 // TODO investigate and fix the mechanism that leave them there after
301 if (browserCols
.containsKey(currNodePath
)) {
302 FilterEntitiesVirtualTable currCol
= browserCols
.get(currNodePath
);
303 if (currCol
.isDisposed())
304 browserCols
.remove(currNodePath
);
307 if (!browserCols
.containsKey(currNodePath
))
308 createBrowserColumn(colViewer
, node
);
310 colViewer
.setLayout(CmsUtils
.noSpaceGridLayout(new GridLayout(
311 browserCols
.size(), false)));
314 // also resize the scrolled composite
315 scrolledCmp
.layout();
316 scrolledCmp
.getShowFocusedControl();
317 // colViewer.getParent().layout();
318 // if (JcrUtils.parentPath(currNodePath).equals(currBrowserKey)) {
323 private void modifyFilter(boolean fromOutside
) {
325 if (currEdited
!= null) {
326 String filter
= filterTxt
.getText() + "*";
327 FilterEntitiesVirtualTable table
= browserCols
328 .get(getPath(currEdited
));
329 if (table
!= null && !table
.isDisposed())
330 table
.filterList(filter
);
335 private String
getPath(Node node
) {
337 return node
.getPath();
338 } catch (RepositoryException e
) {
339 throw new ArgeoException("Unable to get path for node " + node
, e
);
343 private Point imageWidth
= new Point(250, 0);
346 * Recreates the content of the box that displays information about the
347 * current selected node.
349 private Control
createNodeView(Composite parent
, Node context
)
350 throws RepositoryException
{
352 parent
.setLayout(new GridLayout(2, false));
354 if (isImg(context
)) {
355 EditableImage image
= new Img(parent
, RIGHT
, context
, imageWidth
);
356 image
.setLayoutData(new GridData(SWT
.CENTER
, SWT
.CENTER
, true,
360 // Name and primary type
361 Label contextL
= new Label(parent
, SWT
.NONE
);
362 contextL
.setData(RWT
.MARKUP_ENABLED
, true);
363 contextL
.setText("<b>" + context
.getName() + "</b>");
364 new Label(parent
, SWT
.NONE
).setText(context
.getPrimaryNodeType()
368 for (NodeIterator nIt
= context
.getNodes(); nIt
.hasNext();) {
369 Node child
= nIt
.nextNode();
370 new CmsLink(child
.getName(), BROWSE_PREFIX
+ child
.getPath())
371 .createUi(parent
, context
);
372 new Label(parent
, SWT
.NONE
).setText(child
.getPrimaryNodeType()
377 for (PropertyIterator pIt
= context
.getProperties(); pIt
.hasNext();) {
378 Property property
= pIt
.nextProperty();
379 Label label
= new Label(parent
, SWT
.NONE
);
380 label
.setText(property
.getName());
381 label
.setToolTipText(JcrUtils
382 .getPropertyDefinitionAsString(property
));
383 new Label(parent
, SWT
.NONE
).setText(getPropAsString(property
));
389 private boolean isImg(Node node
) throws RepositoryException
{
390 return node
.hasNode(JCR_CONTENT
) && node
.isNodeType(CmsTypes
.CMS_IMAGE
);
393 private String
getPropAsString(Property property
)
394 throws RepositoryException
{
396 if (property
.isMultiple()) {
397 result
= getMultiAsString(property
, ", ");
399 Value value
= property
.getValue();
400 if (value
.getType() == PropertyType
.BINARY
)
402 else if (value
.getType() == PropertyType
.DATE
)
403 result
= timeFormatter
.format(value
.getDate().getTime());
405 result
= value
.getString();
410 private String
getMultiAsString(Property property
, String separator
)
411 throws RepositoryException
{
412 if (separator
== null)
414 Value
[] values
= property
.getValues();
415 StringBuilder builder
= new StringBuilder();
416 for (Value val
: values
) {
417 String currStr
= val
.getString();
418 if (!"".equals(currStr
.trim()))
419 builder
.append(currStr
).append(separator
);
421 if (builder
.lastIndexOf(separator
) >= 0)
422 return builder
.substring(0, builder
.length() - separator
.length());
424 return builder
.toString();
427 /** Almost canonical implementation of a table that display entities */
428 private class FilterEntitiesVirtualTable
extends Composite
{
429 private static final long serialVersionUID
= 8798147431706283824L;
432 private Node context
;
435 private TableViewer entityViewer
;
437 // enable management of multiple columns
443 public boolean setFocus() {
444 if (entityViewer
.getTable().isDisposed())
446 if (entityViewer
.getSelection().isEmpty()) {
447 Object first
= entityViewer
.getElementAt(0);
449 entityViewer
.setSelection(new StructuredSelection(first
),
453 return entityViewer
.getTable().setFocus();
456 void filterList(String filter
) {
458 NodeIterator nit
= context
.getNodes(filter
);
459 refreshFilteredList(nit
);
460 } catch (RepositoryException e
) {
461 throw new ArgeoException("Unable to filter " + getNode()
462 + " children with filter " + filter
, e
);
467 public FilterEntitiesVirtualTable(Composite parent
, int style
,
469 super(parent
, SWT
.NO_FOCUS
);
470 this.context
= context
;
474 protected void populate() {
475 Composite parent
= this;
476 GridLayout layout
= CmsUtils
.noSpaceGridLayout();
478 this.setLayout(layout
);
479 createTableViewer(parent
);
482 private void createTableViewer(final Composite parent
) {
484 // We must limit the size of the table otherwise the full list is
486 // before the layout happens
487 Composite listCmp
= new Composite(parent
, SWT
.NO_FOCUS
);
488 GridData gd
= new GridData(SWT
.LEFT
, SWT
.FILL
, false, true);
489 gd
.widthHint
= COLUMN_WIDTH
;
490 listCmp
.setLayoutData(gd
);
491 listCmp
.setLayout(CmsUtils
.noSpaceGridLayout());
493 entityViewer
= new TableViewer(listCmp
, SWT
.VIRTUAL
| SWT
.SINGLE
);
494 Table table
= entityViewer
.getTable();
496 table
.setLayoutData(CmsUtils
.fillAll());
497 table
.setLinesVisible(true);
498 table
.setHeaderVisible(false);
499 table
.setData(RWT
.MARKUP_ENABLED
, Boolean
.TRUE
);
501 CmsUtils
.style(table
, MaintenanceStyles
.BROWSER_COLUMN
);
504 TableViewerColumn column
= new TableViewerColumn(entityViewer
,
506 TableColumn tcol
= column
.getColumn();
507 tcol
.setWidth(COLUMN_WIDTH
);
508 tcol
.setResizable(true);
509 column
.setLabelProvider(new SimpleNameLP());
511 entityViewer
.setContentProvider(new MyLazyCP(entityViewer
));
513 .addSelectionChangedListener(new ISelectionChangedListener() {
516 public void selectionChanged(SelectionChangedEvent event
) {
517 IStructuredSelection selection
= (IStructuredSelection
) entityViewer
519 if (selection
.isEmpty())
522 setEdited((Node
) selection
.getFirstElement());
527 table
.addKeyListener(new KeyListener() {
528 private static final long serialVersionUID
= -330694313896036230L;
531 public void keyReleased(KeyEvent e
) {
535 public void keyPressed(KeyEvent e
) {
537 IStructuredSelection selection
= (IStructuredSelection
) entityViewer
539 Node selected
= null;
540 if (!selection
.isEmpty())
541 selected
= ((Node
) selection
.getFirstElement());
543 if (e
.keyCode
== SWT
.ARROW_RIGHT
) {
544 if (selected
!= null) {
546 browserCols
.get(selected
.getPath()).setFocus();
548 } else if (e
.keyCode
== SWT
.ARROW_LEFT
) {
550 selected
= getNode().getParent();
551 String newPath
= selected
.getPath(); // getNode().getParent()
553 if (browserCols
.containsKey(newPath
))
554 browserCols
.get(newPath
).setFocus();
555 } catch (ItemNotFoundException ie
) {
559 } catch (RepositoryException ie
) {
560 throw new ArgeoException("Error while managing arrow "
561 + "events in the browser for " + selected
, ie
);
567 private class MyLazyCP
implements ILazyContentProvider
{
568 private static final long serialVersionUID
= 1L;
569 private TableViewer viewer
;
570 private Object
[] elements
;
572 public MyLazyCP(TableViewer viewer
) {
573 this.viewer
= viewer
;
576 public void dispose() {
579 public void inputChanged(Viewer viewer
, Object oldInput
,
581 // IMPORTANT: don't forget this: an exception will be thrown if
582 // a selected object is not part of the results anymore.
583 viewer
.setSelection(null);
584 this.elements
= (Object
[]) newInput
;
587 public void updateElement(int index
) {
588 viewer
.replace(elements
[index
], index
);
592 protected void refreshFilteredList(NodeIterator children
) {
593 Object
[] rows
= JcrUtils
.nodeIteratorToList(children
).toArray();
594 entityViewer
.setInput(rows
);
595 entityViewer
.setItemCount(rows
.length
);
596 entityViewer
.refresh();
599 public class SimpleNameLP
extends ColumnLabelProvider
{
600 private static final long serialVersionUID
= 2465059387875338553L;
603 public String
getText(Object element
) {
604 if (element
instanceof Node
) {
605 Node curr
= ((Node
) element
);
607 return curr
.getName();
608 } catch (RepositoryException e
) {
609 throw new ArgeoException("Unable to get name for"
613 return super.getText(element
);