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
.argeo
.api
.cms
.CmsLog
;
10 import org
.argeo
.eclipse
.ui
.EclipseUiUtils
;
11 import org
.eclipse
.jface
.viewers
.ISelectionChangedListener
;
12 import org
.eclipse
.jface
.viewers
.IStructuredSelection
;
13 import org
.eclipse
.jface
.viewers
.SelectionChangedEvent
;
14 import org
.eclipse
.jface
.viewers
.StructuredSelection
;
15 import org
.eclipse
.swt
.SWT
;
16 import org
.eclipse
.swt
.custom
.SashForm
;
17 import org
.eclipse
.swt
.custom
.ScrolledComposite
;
18 import org
.eclipse
.swt
.events
.ControlAdapter
;
19 import org
.eclipse
.swt
.events
.ControlEvent
;
20 import org
.eclipse
.swt
.events
.KeyEvent
;
21 import org
.eclipse
.swt
.events
.KeyListener
;
22 import org
.eclipse
.swt
.events
.ModifyEvent
;
23 import org
.eclipse
.swt
.events
.ModifyListener
;
24 import org
.eclipse
.swt
.graphics
.Rectangle
;
25 import org
.eclipse
.swt
.layout
.GridData
;
26 import org
.eclipse
.swt
.layout
.GridLayout
;
27 import org
.eclipse
.swt
.widgets
.Composite
;
28 import org
.eclipse
.swt
.widgets
.Control
;
29 import org
.eclipse
.swt
.widgets
.Label
;
30 import org
.eclipse
.swt
.widgets
.Table
;
31 import org
.eclipse
.swt
.widgets
.Text
;
33 /** Simple UI provider that populates a composite parent given a NIO path */
34 public class AdvancedFsBrowser
{
35 private final static CmsLog log
= CmsLog
.getLog(AdvancedFsBrowser
.class);
37 // Some local constants to experiment. should be cleaned
38 // private final static int THUMBNAIL_WIDTH = 400;
39 // private Point imageWidth = new Point(250, 0);
40 private final static int COLUMN_WIDTH
= 160;
42 private Path initialPath
;
43 private Path currEdited
;
45 private Composite displayBoxCmp
;
46 private Text parentPathTxt
;
47 private Text filterTxt
;
49 private ScrolledComposite scrolledCmp
;
50 // Keep a cache of the opened directories
51 private LinkedHashMap
<Path
, FilterEntitiesVirtualTable
> browserCols
= new LinkedHashMap
<>();
52 private Composite scrolledCmpBody
;
54 public Control
createUi(Composite parent
, Path basePath
) {
56 throw new IllegalArgumentException("Context cannot be null");
57 parent
.setLayout(new GridLayout());
60 Composite filterCmp
= new Composite(parent
, SWT
.NO_FOCUS
);
61 filterCmp
.setLayoutData(EclipseUiUtils
.fillWidth());
62 addFilterPanel(filterCmp
);
64 // Bottom part a sash with browser on the left
65 SashForm form
= new SashForm(parent
, SWT
.HORIZONTAL
);
66 // form.setLayout(new FillLayout());
67 form
.setLayoutData(EclipseUiUtils
.fillAll());
68 Composite leftCmp
= new Composite(form
, SWT
.NO_FOCUS
);
69 displayBoxCmp
= new Composite(form
, SWT
.NONE
);
70 form
.setWeights(new int[] { 3, 1 });
72 createBrowserPart(leftCmp
, basePath
);
73 // leftCmp.addControlListener(new ControlAdapter() {
75 // public void controlResized(ControlEvent e) {
76 // Rectangle r = leftCmp.getClientArea();
77 // log.warn("Browser resized: " + r.toString());
78 // scrolledCmp.setMinSize(browserCols.size() * (COLUMN_WIDTH + 2),
80 // // scrolledCmp.setMinSize(scrolledCmpBody.computeSize(SWT.DEFAULT,
85 populateCurrEditedDisplay(displayBoxCmp
, basePath
);
89 initialPath
= basePath
;
90 // form.layout(true, true);
94 private void createBrowserPart(Composite parent
, Path context
) {
95 parent
.setLayout(EclipseUiUtils
.noSpaceGridLayout());
98 scrolledCmp
= new ScrolledComposite(parent
, SWT
.H_SCROLL
| SWT
.BORDER
| SWT
.NO_FOCUS
);
99 scrolledCmp
.setLayoutData(new GridData(SWT
.FILL
, SWT
.FILL
, true, true));
100 scrolledCmp
.setExpandVertical(true);
101 scrolledCmp
.setExpandHorizontal(true);
102 scrolledCmp
.setShowFocusedControl(true);
104 scrolledCmpBody
= new Composite(scrolledCmp
, SWT
.NO_FOCUS
);
105 scrolledCmp
.setContent(scrolledCmpBody
);
106 scrolledCmpBody
.addControlListener(new ControlAdapter() {
107 private static final long serialVersionUID
= 183238447102854553L;
110 public void controlResized(ControlEvent e
) {
111 Rectangle r
= scrolledCmp
.getClientArea();
112 scrolledCmp
.setMinSize(scrolledCmpBody
.computeSize(SWT
.DEFAULT
, r
.height
));
115 initExplorer(scrolledCmpBody
, context
);
116 scrolledCmpBody
.layout(true, true);
117 scrolledCmp
.layout();
121 private Control
initExplorer(Composite parent
, Path context
) {
122 parent
.setLayout(EclipseUiUtils
.noSpaceGridLayout());
123 return createBrowserColumn(parent
, context
);
126 private Control
createBrowserColumn(Composite parent
, Path context
) {
127 // TODO style is not correctly managed.
128 FilterEntitiesVirtualTable table
= new FilterEntitiesVirtualTable(parent
, SWT
.BORDER
| SWT
.NO_FOCUS
, context
);
129 // CmsUtils.style(table, ArgeoOrgStyle.browserColumn.style());
130 table
.filterList("*");
131 table
.setLayoutData(new GridData(SWT
.LEFT
, SWT
.FILL
, false, true));
132 browserCols
.put(context
, table
);
133 parent
.layout(true, true);
137 public void addFilterPanel(Composite parent
) {
138 parent
.setLayout(EclipseUiUtils
.noSpaceGridLayout(new GridLayout(2, false)));
140 parentPathTxt
= new Text(parent
, SWT
.NO_FOCUS
);
141 parentPathTxt
.setEditable(false);
143 filterTxt
= new Text(parent
, SWT
.SEARCH
| SWT
.ICON_CANCEL
);
144 filterTxt
.setMessage("Filter current list");
145 filterTxt
.setLayoutData(EclipseUiUtils
.fillWidth());
146 filterTxt
.addModifyListener(new ModifyListener() {
147 private static final long serialVersionUID
= 1L;
149 public void modifyText(ModifyEvent event
) {
153 filterTxt
.addKeyListener(new KeyListener() {
154 private static final long serialVersionUID
= 2533535233583035527L;
157 public void keyReleased(KeyEvent e
) {
161 public void keyPressed(KeyEvent e
) {
162 boolean shiftPressed
= (e
.stateMask
& SWT
.SHIFT
) != 0;
163 // boolean altPressed = (e.stateMask & SWT.ALT) != 0;
164 FilterEntitiesVirtualTable currTable
= null;
165 if (currEdited
!= null) {
166 FilterEntitiesVirtualTable table
= browserCols
.get(currEdited
);
167 if (table
!= null && !table
.isDisposed())
171 if (e
.keyCode
== SWT
.ARROW_DOWN
)
172 currTable
.setFocus();
173 else if (e
.keyCode
== SWT
.BS
) {
174 if (filterTxt
.getText().equals("")
175 && !(currEdited
.getNameCount() == 1 || currEdited
.equals(initialPath
))) {
176 Path oldEdited
= currEdited
;
177 Path parentPath
= currEdited
.getParent();
178 setEdited(parentPath
);
179 if (browserCols
.containsKey(parentPath
))
180 browserCols
.get(parentPath
).setSelected(oldEdited
);
181 filterTxt
.setFocus();
184 } else if (e
.keyCode
== SWT
.TAB
&& !shiftPressed
) {
185 Path uniqueChild
= getOnlyChild(currEdited
, filterTxt
.getText());
186 if (uniqueChild
!= null) {
187 // Highlight the unique chosen child
188 currTable
.setSelected(uniqueChild
);
189 setEdited(uniqueChild
);
191 filterTxt
.setFocus();
198 private Path
getOnlyChild(Path parent
, String filter
) {
199 try (DirectoryStream
<Path
> stream
= Files
.newDirectoryStream(currEdited
, filter
+ "*")) {
200 Path uniqueChild
= null;
201 boolean moreThanOne
= false;
202 loop
: for (Path entry
: stream
) {
203 if (uniqueChild
== null) {
213 } catch (IOException ioe
) {
214 throw new FsUiException(
215 "Unable to determine unique child existence and get it under " + parent
+ " with filter " + filter
,
220 private void setEdited(Path path
) {
222 EclipseUiUtils
.clear(displayBoxCmp
);
223 populateCurrEditedDisplay(displayBoxCmp
, currEdited
);
224 refreshFilters(path
);
225 refreshBrowser(path
);
228 private void refreshFilters(Path path
) {
229 parentPathTxt
.setText(path
.toUri().toString());
230 filterTxt
.setText("");
231 filterTxt
.getParent().layout();
234 private void refreshBrowser(Path currPath
) {
235 Path currParPath
= currPath
.getParent();
236 Object
[][] colMatrix
= new Object
[browserCols
.size()][2];
238 int i
= 0, currPathIndex
= -1, lastLeftOpenedIndex
= -1;
239 for (Path path
: browserCols
.keySet()) {
240 colMatrix
[i
][0] = path
;
241 colMatrix
[i
][1] = browserCols
.get(path
);
242 if (currPathIndex
>= 0 && lastLeftOpenedIndex
< 0 && currParPath
!= null) {
243 boolean leaveOpened
= path
.startsWith(currPath
);
245 lastLeftOpenedIndex
= i
;
247 if (currParPath
.equals(path
))
252 if (currPathIndex
>= 0 && lastLeftOpenedIndex
>= 0) {
253 // dispose and remove useless cols
254 for (int l
= i
- 1; l
>= lastLeftOpenedIndex
; l
--) {
255 ((FilterEntitiesVirtualTable
) colMatrix
[l
][1]).dispose();
256 browserCols
.remove(colMatrix
[l
][0]);
260 if (browserCols
.containsKey(currPath
)) {
261 FilterEntitiesVirtualTable currCol
= browserCols
.get(currPath
);
262 if (currCol
.isDisposed()) {
263 // Does it still happen ?
264 log
.warn(currPath
+ " browser column was disposed and still listed");
265 browserCols
.remove(currPath
);
269 if (!browserCols
.containsKey(currPath
) && Files
.isDirectory(currPath
))
270 createBrowserColumn(scrolledCmpBody
, currPath
);
272 scrolledCmpBody
.setLayout(EclipseUiUtils
.noSpaceGridLayout(new GridLayout(browserCols
.size(), false)));
273 scrolledCmpBody
.layout(true, true);
274 // also resize the scrolled composite
275 scrolledCmp
.layout();
278 private void modifyFilter(boolean fromOutside
) {
280 if (currEdited
!= null) {
281 String filter
= filterTxt
.getText() + "*";
282 FilterEntitiesVirtualTable table
= browserCols
.get(currEdited
);
283 if (table
!= null && !table
.isDisposed())
284 table
.filterList(filter
);
289 * Recreates the content of the box that displays information about the current
292 private void populateCurrEditedDisplay(Composite parent
, Path context
) {
293 parent
.setLayout(new GridLayout());
295 // if (isImg(context)) {
296 // EditableImage image = new Img(parent, RIGHT, context, imageWidth);
297 // image.setLayoutData(new GridData(SWT.CENTER, SWT.CENTER, true, false,
302 Label contextL
= new Label(parent
, SWT
.NONE
);
303 contextL
.setText(context
.getFileName().toString());
304 contextL
.setFont(EclipseUiUtils
.getBoldFont(parent
));
305 addProperty(parent
, "Last modified", Files
.getLastModifiedTime(context
).toString());
306 addProperty(parent
, "Owner", Files
.getOwner(context
).getName());
307 if (Files
.isDirectory(context
)) {
308 addProperty(parent
, "Type", "Folder");
310 String mimeType
= Files
.probeContentType(context
);
311 if (EclipseUiUtils
.isEmpty(mimeType
))
312 mimeType
= "<i>Unknown</i>";
313 addProperty(parent
, "Type", mimeType
);
314 addProperty(parent
, "Size", FsUiUtils
.humanReadableByteCount(Files
.size(context
), false));
316 parent
.layout(true, true);
317 } catch (IOException e
) {
318 throw new FsUiException("Cannot display details for " + context
, e
);
322 private void addProperty(Composite parent
, String propName
, String value
) {
323 Label contextL
= new Label(parent
, SWT
.NONE
);
324 contextL
.setText(propName
+ ": " + value
);
328 * Almost canonical implementation of a table that displays the content of a
331 private class FilterEntitiesVirtualTable
extends Composite
{
332 private static final long serialVersionUID
= 2223410043691844875L;
335 private Path context
;
336 private Path currSelected
= null;
339 private FsTableViewer viewer
;
342 public boolean setFocus() {
343 if (viewer
.getTable().isDisposed())
345 if (currSelected
!= null)
346 viewer
.setSelection(new StructuredSelection(currSelected
), true);
347 else if (viewer
.getSelection().isEmpty()) {
348 Object first
= viewer
.getElementAt(0);
350 viewer
.setSelection(new StructuredSelection(first
), true);
352 return viewer
.getTable().setFocus();
356 * Enable highlighting the correct element in the table when externally browsing
357 * (typically via the command-line-like Text field)
359 void setSelected(Path selected
) {
360 // to prevent change selection event to be thrown
361 currSelected
= selected
;
362 viewer
.setSelection(new StructuredSelection(currSelected
), true);
365 void filterList(String filter
) {
366 viewer
.setInput(context
, filter
);
369 public FilterEntitiesVirtualTable(Composite parent
, int style
, Path context
) {
370 super(parent
, SWT
.NO_FOCUS
);
371 this.context
= context
;
372 createTableViewer(this);
375 private void createTableViewer(final Composite parent
) {
376 parent
.setLayout(EclipseUiUtils
.noSpaceGridLayout());
378 // We must limit the size of the table otherwise the full list is
379 // loaded before the layout happens
380 // Composite listCmp = new Composite(parent, SWT.NO_FOCUS);
381 // GridData gd = new GridData(SWT.LEFT, SWT.FILL, false, true);
382 // gd.widthHint = COLUMN_WIDTH;
383 // listCmp.setLayoutData(gd);
384 // listCmp.setLayout(EclipseUiUtils.noSpaceGridLayout());
385 // viewer = new TableViewer(listCmp, SWT.VIRTUAL | SWT.MULTI |
387 // Table table = viewer.getTable();
388 // table.setLayoutData(EclipseUiUtils.fillAll());
390 viewer
= new FsTableViewer(parent
, SWT
.MULTI
);
391 Table table
= viewer
.configureDefaultSingleColumnTable(COLUMN_WIDTH
);
393 viewer
.addSelectionChangedListener(new ISelectionChangedListener() {
396 public void selectionChanged(SelectionChangedEvent event
) {
397 IStructuredSelection selection
= (IStructuredSelection
) viewer
.getSelection();
398 if (selection
.isEmpty())
400 Object obj
= selection
.getFirstElement();
402 if (obj
instanceof Path
)
403 newSelected
= (Path
) obj
;
404 else if (obj
instanceof ParentDir
)
405 newSelected
= ((ParentDir
) obj
).getPath();
408 if (newSelected
.equals(currSelected
))
410 currSelected
= newSelected
;
411 setEdited(newSelected
);
416 table
.addKeyListener(new KeyListener() {
417 private static final long serialVersionUID
= -8083424284436715709L;
420 public void keyReleased(KeyEvent e
) {
424 public void keyPressed(KeyEvent e
) {
425 IStructuredSelection selection
= (IStructuredSelection
) viewer
.getSelection();
426 Path selected
= null;
427 if (!selection
.isEmpty())
428 selected
= ((Path
) selection
.getFirstElement());
429 if (e
.keyCode
== SWT
.ARROW_RIGHT
) {
430 if (!Files
.isDirectory(selected
))
432 if (selected
!= null) {
434 browserCols
.get(selected
).setFocus();
436 } else if (e
.keyCode
== SWT
.ARROW_LEFT
) {
437 if (context
.equals(initialPath
))
439 Path parent
= context
.getParent();
444 browserCols
.get(parent
).setFocus();