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
;
34 /** Simple UI provider that populates a composite parent given a NIO path */
35 public class AdvancedFsBrowser
{
36 private final static Log log
= LogFactory
.getLog(AdvancedFsBrowser
.class);
38 // Some local constants to experiment. should be cleaned
39 // private final static int THUMBNAIL_WIDTH = 400;
40 // private Point imageWidth = new Point(250, 0);
41 private final static int COLUMN_WIDTH
= 160;
43 private Path initialPath
;
44 private Path currEdited
;
46 private Composite displayBoxCmp
;
47 private Text parentPathTxt
;
48 private Text filterTxt
;
50 private ScrolledComposite scrolledCmp
;
51 // Keep a cache of the opened directories
52 private LinkedHashMap
<Path
, FilterEntitiesVirtualTable
> browserCols
= new LinkedHashMap
<>();
53 private Composite scrolledCmpBody
;
55 public Control
createUi(Composite parent
, Path basePath
) {
57 throw new IllegalArgumentException("Context cannot be null");
58 parent
.setLayout(new GridLayout());
61 Composite filterCmp
= new Composite(parent
, SWT
.NO_FOCUS
);
62 filterCmp
.setLayoutData(EclipseUiUtils
.fillWidth());
63 addFilterPanel(filterCmp
);
65 // Bottom part a sash with browser on the left
66 SashForm form
= new SashForm(parent
, SWT
.HORIZONTAL
);
67 // form.setLayout(new FillLayout());
68 form
.setLayoutData(EclipseUiUtils
.fillAll());
69 Composite leftCmp
= new Composite(form
, SWT
.NO_FOCUS
);
70 displayBoxCmp
= new Composite(form
, SWT
.NONE
);
71 form
.setWeights(new int[] { 3, 1 });
73 createBrowserPart(leftCmp
, basePath
);
74 // leftCmp.addControlListener(new ControlAdapter() {
76 // public void controlResized(ControlEvent e) {
77 // Rectangle r = leftCmp.getClientArea();
78 // log.warn("Browser resized: " + r.toString());
79 // scrolledCmp.setMinSize(browserCols.size() * (COLUMN_WIDTH + 2),
81 // // scrolledCmp.setMinSize(scrolledCmpBody.computeSize(SWT.DEFAULT,
86 populateCurrEditedDisplay(displayBoxCmp
, basePath
);
90 initialPath
= basePath
;
91 // form.layout(true, true);
95 private void createBrowserPart(Composite parent
, Path context
) {
96 parent
.setLayout(EclipseUiUtils
.noSpaceGridLayout());
99 scrolledCmp
= new ScrolledComposite(parent
, SWT
.H_SCROLL
| SWT
.BORDER
| SWT
.NO_FOCUS
);
100 scrolledCmp
.setLayoutData(new GridData(SWT
.FILL
, SWT
.FILL
, true, true));
101 scrolledCmp
.setExpandVertical(true);
102 scrolledCmp
.setExpandHorizontal(true);
103 scrolledCmp
.setShowFocusedControl(true);
105 scrolledCmpBody
= new Composite(scrolledCmp
, SWT
.NO_FOCUS
);
106 scrolledCmp
.setContent(scrolledCmpBody
);
107 scrolledCmpBody
.addControlListener(new ControlAdapter() {
108 private static final long serialVersionUID
= 183238447102854553L;
111 public void controlResized(ControlEvent e
) {
112 Rectangle r
= scrolledCmp
.getClientArea();
113 scrolledCmp
.setMinSize(scrolledCmpBody
.computeSize(SWT
.DEFAULT
, r
.height
));
116 initExplorer(scrolledCmpBody
, context
);
117 scrolledCmpBody
.layout(true, true);
118 scrolledCmp
.layout();
122 private Control
initExplorer(Composite parent
, Path context
) {
123 parent
.setLayout(EclipseUiUtils
.noSpaceGridLayout());
124 return createBrowserColumn(parent
, context
);
127 private Control
createBrowserColumn(Composite parent
, Path context
) {
128 // TODO style is not correctly managed.
129 FilterEntitiesVirtualTable table
= new FilterEntitiesVirtualTable(parent
, SWT
.BORDER
| SWT
.NO_FOCUS
, context
);
130 // CmsUtils.style(table, ArgeoOrgStyle.browserColumn.style());
131 table
.filterList("*");
132 table
.setLayoutData(new GridData(SWT
.LEFT
, SWT
.FILL
, false, true));
133 browserCols
.put(context
, table
);
134 parent
.layout(true, true);
138 public void addFilterPanel(Composite parent
) {
139 parent
.setLayout(EclipseUiUtils
.noSpaceGridLayout(new GridLayout(2, false)));
141 parentPathTxt
= new Text(parent
, SWT
.NO_FOCUS
);
142 parentPathTxt
.setEditable(false);
144 filterTxt
= new Text(parent
, SWT
.SEARCH
| SWT
.ICON_CANCEL
);
145 filterTxt
.setMessage("Filter current list");
146 filterTxt
.setLayoutData(EclipseUiUtils
.fillWidth());
147 filterTxt
.addModifyListener(new ModifyListener() {
148 private static final long serialVersionUID
= 1L;
150 public void modifyText(ModifyEvent event
) {
154 filterTxt
.addKeyListener(new KeyListener() {
155 private static final long serialVersionUID
= 2533535233583035527L;
158 public void keyReleased(KeyEvent e
) {
162 public void keyPressed(KeyEvent e
) {
163 boolean shiftPressed
= (e
.stateMask
& SWT
.SHIFT
) != 0;
164 // boolean altPressed = (e.stateMask & SWT.ALT) != 0;
165 FilterEntitiesVirtualTable currTable
= null;
166 if (currEdited
!= null) {
167 FilterEntitiesVirtualTable table
= browserCols
.get(currEdited
);
168 if (table
!= null && !table
.isDisposed())
172 if (e
.keyCode
== SWT
.ARROW_DOWN
)
173 currTable
.setFocus();
174 else if (e
.keyCode
== SWT
.BS
) {
175 if (filterTxt
.getText().equals("")
176 && !(currEdited
.getNameCount() == 1 || currEdited
.equals(initialPath
))) {
177 Path oldEdited
= currEdited
;
178 Path parentPath
= currEdited
.getParent();
179 setEdited(parentPath
);
180 if (browserCols
.containsKey(parentPath
))
181 browserCols
.get(parentPath
).setSelected(oldEdited
);
182 filterTxt
.setFocus();
185 } else if (e
.keyCode
== SWT
.TAB
&& !shiftPressed
) {
186 Path uniqueChild
= getOnlyChild(currEdited
, filterTxt
.getText());
187 if (uniqueChild
!= null) {
188 // Highlight the unique chosen child
189 currTable
.setSelected(uniqueChild
);
190 setEdited(uniqueChild
);
192 filterTxt
.setFocus();
199 private Path
getOnlyChild(Path parent
, String filter
) {
200 try (DirectoryStream
<Path
> stream
= Files
.newDirectoryStream(currEdited
, filter
+ "*")) {
201 Path uniqueChild
= null;
202 boolean moreThanOne
= false;
203 loop
: for (Path entry
: stream
) {
204 if (uniqueChild
== null) {
214 } catch (IOException ioe
) {
215 throw new FsUiException(
216 "Unable to determine unique child existence and get it under " + parent
+ " with filter " + filter
,
221 private void setEdited(Path path
) {
223 EclipseUiUtils
.clear(displayBoxCmp
);
224 populateCurrEditedDisplay(displayBoxCmp
, currEdited
);
225 refreshFilters(path
);
226 refreshBrowser(path
);
229 private void refreshFilters(Path path
) {
230 parentPathTxt
.setText(path
.toUri().toString());
231 filterTxt
.setText("");
232 filterTxt
.getParent().layout();
235 private void refreshBrowser(Path currPath
) {
236 Path currParPath
= currPath
.getParent();
237 Object
[][] colMatrix
= new Object
[browserCols
.size()][2];
239 int i
= 0, currPathIndex
= -1, lastLeftOpenedIndex
= -1;
240 for (Path path
: browserCols
.keySet()) {
241 colMatrix
[i
][0] = path
;
242 colMatrix
[i
][1] = browserCols
.get(path
);
243 if (currPathIndex
>= 0 && lastLeftOpenedIndex
< 0 && currParPath
!= null) {
244 boolean leaveOpened
= path
.startsWith(currPath
);
246 lastLeftOpenedIndex
= i
;
248 if (currParPath
.equals(path
))
253 if (currPathIndex
>= 0 && lastLeftOpenedIndex
>= 0) {
254 // dispose and remove useless cols
255 for (int l
= i
- 1; l
>= lastLeftOpenedIndex
; l
--) {
256 ((FilterEntitiesVirtualTable
) colMatrix
[l
][1]).dispose();
257 browserCols
.remove(colMatrix
[l
][0]);
261 if (browserCols
.containsKey(currPath
)) {
262 FilterEntitiesVirtualTable currCol
= browserCols
.get(currPath
);
263 if (currCol
.isDisposed()) {
264 // Does it still happen ?
265 log
.warn(currPath
+ " browser column was disposed and still listed");
266 browserCols
.remove(currPath
);
270 if (!browserCols
.containsKey(currPath
) && Files
.isDirectory(currPath
))
271 createBrowserColumn(scrolledCmpBody
, currPath
);
273 scrolledCmpBody
.setLayout(EclipseUiUtils
.noSpaceGridLayout(new GridLayout(browserCols
.size(), false)));
274 scrolledCmpBody
.layout(true, true);
275 // also resize the scrolled composite
276 scrolledCmp
.layout();
279 private void modifyFilter(boolean fromOutside
) {
281 if (currEdited
!= null) {
282 String filter
= filterTxt
.getText() + "*";
283 FilterEntitiesVirtualTable table
= browserCols
.get(currEdited
);
284 if (table
!= null && !table
.isDisposed())
285 table
.filterList(filter
);
290 * Recreates the content of the box that displays information about the current
293 private void populateCurrEditedDisplay(Composite parent
, Path context
) {
294 parent
.setLayout(new GridLayout());
296 // if (isImg(context)) {
297 // EditableImage image = new Img(parent, RIGHT, context, imageWidth);
298 // image.setLayoutData(new GridData(SWT.CENTER, SWT.CENTER, true, false,
303 Label contextL
= new Label(parent
, SWT
.NONE
);
304 contextL
.setText(context
.getFileName().toString());
305 contextL
.setFont(EclipseUiUtils
.getBoldFont(parent
));
306 addProperty(parent
, "Last modified", Files
.getLastModifiedTime(context
).toString());
307 addProperty(parent
, "Owner", Files
.getOwner(context
).getName());
308 if (Files
.isDirectory(context
)) {
309 addProperty(parent
, "Type", "Folder");
311 String mimeType
= Files
.probeContentType(context
);
312 if (EclipseUiUtils
.isEmpty(mimeType
))
313 mimeType
= "<i>Unknown</i>";
314 addProperty(parent
, "Type", mimeType
);
315 addProperty(parent
, "Size", FsUiUtils
.humanReadableByteCount(Files
.size(context
), false));
317 parent
.layout(true, true);
318 } catch (IOException e
) {
319 throw new FsUiException("Cannot display details for " + context
, e
);
323 private void addProperty(Composite parent
, String propName
, String value
) {
324 Label contextL
= new Label(parent
, SWT
.NONE
);
325 contextL
.setText(propName
+ ": " + value
);
329 * Almost canonical implementation of a table that displays the content of a
332 private class FilterEntitiesVirtualTable
extends Composite
{
333 private static final long serialVersionUID
= 2223410043691844875L;
336 private Path context
;
337 private Path currSelected
= null;
340 private FsTableViewer viewer
;
343 public boolean setFocus() {
344 if (viewer
.getTable().isDisposed())
346 if (currSelected
!= null)
347 viewer
.setSelection(new StructuredSelection(currSelected
), true);
348 else if (viewer
.getSelection().isEmpty()) {
349 Object first
= viewer
.getElementAt(0);
351 viewer
.setSelection(new StructuredSelection(first
), true);
353 return viewer
.getTable().setFocus();
357 * Enable highlighting the correct element in the table when externally browsing
358 * (typically via the command-line-like Text field)
360 void setSelected(Path selected
) {
361 // to prevent change selection event to be thrown
362 currSelected
= selected
;
363 viewer
.setSelection(new StructuredSelection(currSelected
), true);
366 void filterList(String filter
) {
367 viewer
.setInput(context
, filter
);
370 public FilterEntitiesVirtualTable(Composite parent
, int style
, Path context
) {
371 super(parent
, SWT
.NO_FOCUS
);
372 this.context
= context
;
373 createTableViewer(this);
376 private void createTableViewer(final Composite parent
) {
377 parent
.setLayout(EclipseUiUtils
.noSpaceGridLayout());
379 // We must limit the size of the table otherwise the full list is
380 // loaded before the layout happens
381 // Composite listCmp = new Composite(parent, SWT.NO_FOCUS);
382 // GridData gd = new GridData(SWT.LEFT, SWT.FILL, false, true);
383 // gd.widthHint = COLUMN_WIDTH;
384 // listCmp.setLayoutData(gd);
385 // listCmp.setLayout(EclipseUiUtils.noSpaceGridLayout());
386 // viewer = new TableViewer(listCmp, SWT.VIRTUAL | SWT.MULTI |
388 // Table table = viewer.getTable();
389 // table.setLayoutData(EclipseUiUtils.fillAll());
391 viewer
= new FsTableViewer(parent
, SWT
.MULTI
);
392 Table table
= viewer
.configureDefaultSingleColumnTable(COLUMN_WIDTH
);
394 viewer
.addSelectionChangedListener(new ISelectionChangedListener() {
397 public void selectionChanged(SelectionChangedEvent event
) {
398 IStructuredSelection selection
= (IStructuredSelection
) viewer
.getSelection();
399 if (selection
.isEmpty())
401 Object obj
= selection
.getFirstElement();
403 if (obj
instanceof Path
)
404 newSelected
= (Path
) obj
;
405 else if (obj
instanceof ParentDir
)
406 newSelected
= ((ParentDir
) obj
).getPath();
409 if (newSelected
.equals(currSelected
))
411 currSelected
= newSelected
;
412 setEdited(newSelected
);
417 table
.addKeyListener(new KeyListener() {
418 private static final long serialVersionUID
= -8083424284436715709L;
421 public void keyReleased(KeyEvent e
) {
425 public void keyPressed(KeyEvent e
) {
426 IStructuredSelection selection
= (IStructuredSelection
) viewer
.getSelection();
427 Path selected
= null;
428 if (!selection
.isEmpty())
429 selected
= ((Path
) selection
.getFirstElement());
430 if (e
.keyCode
== SWT
.ARROW_RIGHT
) {
431 if (!Files
.isDirectory(selected
))
433 if (selected
!= null) {
435 browserCols
.get(selected
).setFocus();
437 } else if (e
.keyCode
== SWT
.ARROW_LEFT
) {
438 if (context
.equals(initialPath
))
440 Path parent
= context
.getParent();
445 browserCols
.get(parent
).setFocus();