1 package org
.argeo
.eclipse
.ui
.fs
;
3 import java
.io
.IOException
;
4 import java
.nio
.file
.DirectoryStream
;
5 import java
.nio
.file
.Files
;
6 import java
.nio
.file
.Path
;
7 import java
.util
.LinkedHashMap
;
9 import org
.apache
.commons
.logging
.Log
;
10 import org
.apache
.commons
.logging
.LogFactory
;
11 import org
.argeo
.eclipse
.ui
.EclipseUiUtils
;
12 import org
.eclipse
.jface
.viewers
.ISelectionChangedListener
;
13 import org
.eclipse
.jface
.viewers
.IStructuredSelection
;
14 import org
.eclipse
.jface
.viewers
.SelectionChangedEvent
;
15 import org
.eclipse
.jface
.viewers
.StructuredSelection
;
16 import org
.eclipse
.swt
.SWT
;
17 import org
.eclipse
.swt
.custom
.SashForm
;
18 import org
.eclipse
.swt
.custom
.ScrolledComposite
;
19 import org
.eclipse
.swt
.events
.ControlAdapter
;
20 import org
.eclipse
.swt
.events
.ControlEvent
;
21 import org
.eclipse
.swt
.events
.KeyEvent
;
22 import org
.eclipse
.swt
.events
.KeyListener
;
23 import org
.eclipse
.swt
.events
.ModifyEvent
;
24 import org
.eclipse
.swt
.events
.ModifyListener
;
25 import org
.eclipse
.swt
.graphics
.Rectangle
;
26 import org
.eclipse
.swt
.layout
.GridData
;
27 import org
.eclipse
.swt
.layout
.GridLayout
;
28 import org
.eclipse
.swt
.widgets
.Composite
;
29 import org
.eclipse
.swt
.widgets
.Control
;
30 import org
.eclipse
.swt
.widgets
.Label
;
31 import org
.eclipse
.swt
.widgets
.Table
;
32 import org
.eclipse
.swt
.widgets
.Text
;
35 /** Simple UI provider that populates a composite parent given a NIO path */
36 public class AdvancedFsBrowser
{
37 private final static Log log
= LogFactory
.getLog(AdvancedFsBrowser
.class);
39 // Some local constants to experiment. should be cleaned
40 // private final static int THUMBNAIL_WIDTH = 400;
41 // private Point imageWidth = new Point(250, 0);
42 private final static int COLUMN_WIDTH
= 160;
44 private Path initialPath
;
45 private Path currEdited
;
47 private Composite displayBoxCmp
;
48 private Text parentPathTxt
;
49 private Text filterTxt
;
51 private ScrolledComposite scrolledCmp
;
52 // Keep a cache of the opened directories
53 private LinkedHashMap
<Path
, FilterEntitiesVirtualTable
> browserCols
= new LinkedHashMap
<>();
54 private Composite scrolledCmpBody
;
56 public Control
createUi(Composite parent
, Path basePath
) {
58 throw new IllegalArgumentException("Context cannot be null");
59 parent
.setLayout(new GridLayout());
62 Composite filterCmp
= new Composite(parent
, SWT
.NO_FOCUS
);
63 filterCmp
.setLayoutData(EclipseUiUtils
.fillWidth());
64 addFilterPanel(filterCmp
);
66 // Bottom part a sash with browser on the left
67 SashForm form
= new SashForm(parent
, SWT
.HORIZONTAL
);
68 // form.setLayout(new FillLayout());
69 form
.setLayoutData(EclipseUiUtils
.fillAll());
70 Composite leftCmp
= new Composite(form
, SWT
.NO_FOCUS
);
71 displayBoxCmp
= new Composite(form
, SWT
.NONE
);
72 form
.setWeights(new int[] { 3, 1 });
74 createBrowserPart(leftCmp
, basePath
);
75 // leftCmp.addControlListener(new ControlAdapter() {
77 // public void controlResized(ControlEvent e) {
78 // Rectangle r = leftCmp.getClientArea();
79 // log.warn("Browser resized: " + r.toString());
80 // scrolledCmp.setMinSize(browserCols.size() * (COLUMN_WIDTH + 2),
82 // // scrolledCmp.setMinSize(scrolledCmpBody.computeSize(SWT.DEFAULT,
87 populateCurrEditedDisplay(displayBoxCmp
, basePath
);
91 initialPath
= basePath
;
92 // form.layout(true, true);
96 private void createBrowserPart(Composite parent
, Path context
) {
97 parent
.setLayout(EclipseUiUtils
.noSpaceGridLayout());
100 scrolledCmp
= new ScrolledComposite(parent
, SWT
.H_SCROLL
| SWT
.BORDER
| SWT
.NO_FOCUS
);
101 scrolledCmp
.setLayoutData(new GridData(SWT
.FILL
, SWT
.FILL
, true, true));
102 scrolledCmp
.setExpandVertical(true);
103 scrolledCmp
.setExpandHorizontal(true);
104 scrolledCmp
.setShowFocusedControl(true);
106 scrolledCmpBody
= new Composite(scrolledCmp
, SWT
.NO_FOCUS
);
107 scrolledCmp
.setContent(scrolledCmpBody
);
108 scrolledCmpBody
.addControlListener(new ControlAdapter() {
109 private static final long serialVersionUID
= 183238447102854553L;
112 public void controlResized(ControlEvent e
) {
113 Rectangle r
= scrolledCmp
.getClientArea();
114 scrolledCmp
.setMinSize(scrolledCmpBody
.computeSize(SWT
.DEFAULT
, r
.height
));
117 initExplorer(scrolledCmpBody
, context
);
118 scrolledCmpBody
.layout(true, true);
119 scrolledCmp
.layout();
123 private Control
initExplorer(Composite parent
, Path context
) {
124 parent
.setLayout(EclipseUiUtils
.noSpaceGridLayout());
125 return createBrowserColumn(parent
, context
);
128 private Control
createBrowserColumn(Composite parent
, Path context
) {
129 // TODO style is not correctly managed.
130 FilterEntitiesVirtualTable table
= new FilterEntitiesVirtualTable(parent
, SWT
.BORDER
| SWT
.NO_FOCUS
, context
);
131 // CmsUtils.style(table, ArgeoOrgStyle.browserColumn.style());
132 table
.filterList("*");
133 table
.setLayoutData(new GridData(SWT
.LEFT
, SWT
.FILL
, false, true));
134 browserCols
.put(context
, table
);
135 parent
.layout(true, true);
139 public void addFilterPanel(Composite parent
) {
140 parent
.setLayout(EclipseUiUtils
.noSpaceGridLayout(new GridLayout(2, false)));
142 parentPathTxt
= new Text(parent
, SWT
.NO_FOCUS
);
143 parentPathTxt
.setEditable(false);
145 filterTxt
= new Text(parent
, SWT
.SEARCH
| SWT
.ICON_CANCEL
);
146 filterTxt
.setMessage("Filter current list");
147 filterTxt
.setLayoutData(EclipseUiUtils
.fillWidth());
148 filterTxt
.addModifyListener(new ModifyListener() {
149 private static final long serialVersionUID
= 1L;
151 public void modifyText(ModifyEvent event
) {
155 filterTxt
.addKeyListener(new KeyListener() {
156 private static final long serialVersionUID
= 2533535233583035527L;
159 public void keyReleased(KeyEvent e
) {
163 public void keyPressed(KeyEvent e
) {
164 boolean shiftPressed
= (e
.stateMask
& SWT
.SHIFT
) != 0;
165 // boolean altPressed = (e.stateMask & SWT.ALT) != 0;
166 FilterEntitiesVirtualTable currTable
= null;
167 if (currEdited
!= null) {
168 FilterEntitiesVirtualTable table
= browserCols
.get(currEdited
);
169 if (table
!= null && !table
.isDisposed())
173 if (e
.keyCode
== SWT
.ARROW_DOWN
)
174 currTable
.setFocus();
175 else if (e
.keyCode
== SWT
.BS
) {
176 if (filterTxt
.getText().equals("")
177 && !(currEdited
.getNameCount() == 1 || currEdited
.equals(initialPath
))) {
178 Path oldEdited
= currEdited
;
179 Path parentPath
= currEdited
.getParent();
180 setEdited(parentPath
);
181 if (browserCols
.containsKey(parentPath
))
182 browserCols
.get(parentPath
).setSelected(oldEdited
);
183 filterTxt
.setFocus();
186 } else if (e
.keyCode
== SWT
.TAB
&& !shiftPressed
) {
187 Path uniqueChild
= getOnlyChild(currEdited
, filterTxt
.getText());
188 if (uniqueChild
!= null) {
189 // Highlight the unique chosen child
190 currTable
.setSelected(uniqueChild
);
191 setEdited(uniqueChild
);
193 filterTxt
.setFocus();
200 private Path
getOnlyChild(Path parent
, String filter
) {
201 try (DirectoryStream
<Path
> stream
= Files
.newDirectoryStream(currEdited
, filter
+ "*")) {
202 Path uniqueChild
= null;
203 boolean moreThanOne
= false;
204 loop
: for (Path entry
: stream
) {
205 if (uniqueChild
== null) {
215 } catch (IOException ioe
) {
216 throw new FsUiException(
217 "Unable to determine unique child existence and get it under " + parent
+ " with filter " + filter
,
222 private void setEdited(Path path
) {
224 EclipseUiUtils
.clear(displayBoxCmp
);
225 populateCurrEditedDisplay(displayBoxCmp
, currEdited
);
226 refreshFilters(path
);
227 refreshBrowser(path
);
230 private void refreshFilters(Path path
) {
231 parentPathTxt
.setText(path
.toUri().toString());
232 filterTxt
.setText("");
233 filterTxt
.getParent().layout();
236 private void refreshBrowser(Path currPath
) {
237 Path currParPath
= currPath
.getParent();
238 Object
[][] colMatrix
= new Object
[browserCols
.size()][2];
240 int i
= 0, currPathIndex
= -1, lastLeftOpenedIndex
= -1;
241 for (Path path
: browserCols
.keySet()) {
242 colMatrix
[i
][0] = path
;
243 colMatrix
[i
][1] = browserCols
.get(path
);
244 if (currPathIndex
>= 0 && lastLeftOpenedIndex
< 0 && currParPath
!= null) {
245 boolean leaveOpened
= path
.startsWith(currPath
);
247 lastLeftOpenedIndex
= i
;
249 if (currParPath
.equals(path
))
254 if (currPathIndex
>= 0 && lastLeftOpenedIndex
>= 0) {
255 // dispose and remove useless cols
256 for (int l
= i
- 1; l
>= lastLeftOpenedIndex
; l
--) {
257 ((FilterEntitiesVirtualTable
) colMatrix
[l
][1]).dispose();
258 browserCols
.remove(colMatrix
[l
][0]);
262 if (browserCols
.containsKey(currPath
)) {
263 FilterEntitiesVirtualTable currCol
= browserCols
.get(currPath
);
264 if (currCol
.isDisposed()) {
265 // Does it still happen ?
266 log
.warn(currPath
+ " browser column was disposed and still listed");
267 browserCols
.remove(currPath
);
271 if (!browserCols
.containsKey(currPath
) && Files
.isDirectory(currPath
))
272 createBrowserColumn(scrolledCmpBody
, currPath
);
274 scrolledCmpBody
.setLayout(EclipseUiUtils
.noSpaceGridLayout(new GridLayout(browserCols
.size(), false)));
275 scrolledCmpBody
.layout(true, true);
276 // also resize the scrolled composite
277 scrolledCmp
.layout();
280 private void modifyFilter(boolean fromOutside
) {
282 if (currEdited
!= null) {
283 String filter
= filterTxt
.getText() + "*";
284 FilterEntitiesVirtualTable table
= browserCols
.get(currEdited
);
285 if (table
!= null && !table
.isDisposed())
286 table
.filterList(filter
);
291 * Recreates the content of the box that displays information about the
292 * current selected node.
294 private void populateCurrEditedDisplay(Composite parent
, Path context
) {
295 parent
.setLayout(new GridLayout());
297 // if (isImg(context)) {
298 // EditableImage image = new Img(parent, RIGHT, context, imageWidth);
299 // image.setLayoutData(new GridData(SWT.CENTER, SWT.CENTER, true, false,
304 Label contextL
= new Label(parent
, SWT
.NONE
);
305 contextL
.setText(context
.getFileName().toString());
306 contextL
.setFont(EclipseUiUtils
.getBoldFont(parent
));
307 addProperty(parent
, "Last modified", Files
.getLastModifiedTime(context
).toString());
308 addProperty(parent
, "Owner", Files
.getOwner(context
).getName());
309 if (Files
.isDirectory(context
)) {
310 addProperty(parent
, "Type", "Folder");
312 String mimeType
= Files
.probeContentType(context
);
313 if (EclipseUiUtils
.isEmpty(mimeType
))
314 mimeType
= "<i>Unknown</i>";
315 addProperty(parent
, "Type", mimeType
);
316 addProperty(parent
, "Size", FsUiUtils
.humanReadableByteCount(Files
.size(context
), false));
318 parent
.layout(true, true);
319 } catch (IOException e
) {
320 throw new FsUiException("Cannot display details for " + context
, e
);
324 private void addProperty(Composite parent
, String propName
, String value
) {
325 Label contextL
= new Label(parent
, SWT
.NONE
);
326 contextL
.setText(propName
+ ": " + value
);
330 * Almost canonical implementation of a table that displays the content of a
333 private class FilterEntitiesVirtualTable
extends Composite
{
334 private static final long serialVersionUID
= 2223410043691844875L;
337 private Path context
;
338 private Path currSelected
= null;
341 private FsTableViewer viewer
;
344 public boolean setFocus() {
345 if (viewer
.getTable().isDisposed())
347 if (currSelected
!= null)
348 viewer
.setSelection(new StructuredSelection(currSelected
), true);
349 else if (viewer
.getSelection().isEmpty()) {
350 Object first
= viewer
.getElementAt(0);
352 viewer
.setSelection(new StructuredSelection(first
), true);
354 return viewer
.getTable().setFocus();
358 * Enable highlighting the correct element in the table when externally
359 * browsing (typically via the command-line-like Text field)
361 void setSelected(Path selected
) {
362 // to prevent change selection event to be thrown
363 currSelected
= selected
;
364 viewer
.setSelection(new StructuredSelection(currSelected
), true);
367 void filterList(String filter
) {
368 viewer
.setInput(context
, filter
);
371 public FilterEntitiesVirtualTable(Composite parent
, int style
, Path context
) {
372 super(parent
, SWT
.NO_FOCUS
);
373 this.context
= context
;
374 createTableViewer(this);
377 private void createTableViewer(final Composite parent
) {
378 parent
.setLayout(EclipseUiUtils
.noSpaceGridLayout());
380 // We must limit the size of the table otherwise the full list is
381 // loaded before the layout happens
382 // Composite listCmp = new Composite(parent, SWT.NO_FOCUS);
383 // GridData gd = new GridData(SWT.LEFT, SWT.FILL, false, true);
384 // gd.widthHint = COLUMN_WIDTH;
385 // listCmp.setLayoutData(gd);
386 // listCmp.setLayout(EclipseUiUtils.noSpaceGridLayout());
387 // viewer = new TableViewer(listCmp, SWT.VIRTUAL | SWT.MULTI |
389 // Table table = viewer.getTable();
390 // table.setLayoutData(EclipseUiUtils.fillAll());
392 viewer
= new FsTableViewer(parent
, SWT
.MULTI
);
393 Table table
= viewer
.configureDefaultSingleColumnTable(COLUMN_WIDTH
);
395 viewer
.addSelectionChangedListener(new ISelectionChangedListener() {
398 public void selectionChanged(SelectionChangedEvent event
) {
399 IStructuredSelection selection
= (IStructuredSelection
) viewer
.getSelection();
400 if (selection
.isEmpty())
403 Path newSelected
= (Path
) selection
.getFirstElement();
404 if (newSelected
.equals(currSelected
))
406 currSelected
= newSelected
;
407 setEdited(newSelected
);
412 table
.addKeyListener(new KeyListener() {
413 private static final long serialVersionUID
= -8083424284436715709L;
416 public void keyReleased(KeyEvent e
) {
420 public void keyPressed(KeyEvent e
) {
421 IStructuredSelection selection
= (IStructuredSelection
) viewer
.getSelection();
422 Path selected
= null;
423 if (!selection
.isEmpty())
424 selected
= ((Path
) selection
.getFirstElement());
425 if (e
.keyCode
== SWT
.ARROW_RIGHT
) {
426 if (!Files
.isDirectory(selected
))
428 if (selected
!= null) {
430 browserCols
.get(selected
).setFocus();
432 } else if (e
.keyCode
== SWT
.ARROW_LEFT
) {
433 if (context
.equals(initialPath
))
435 Path parent
= context
.getParent();
440 browserCols
.get(parent
).setFocus();