]> git.argeo.org Git - lgpl/argeo-commons.git/blob - fs/AdvancedFsBrowser.java
Prepare next development cycle
[lgpl/argeo-commons.git] / fs / AdvancedFsBrowser.java
1 package org.argeo.eclipse.ui.fs;
2
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;
8
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;
32
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);
36
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;
41
42 private Path initialPath;
43 private Path currEdited;
44 // Filter
45 private Composite displayBoxCmp;
46 private Text parentPathTxt;
47 private Text filterTxt;
48 // Browser columns
49 private ScrolledComposite scrolledCmp;
50 // Keep a cache of the opened directories
51 private LinkedHashMap<Path, FilterEntitiesVirtualTable> browserCols = new LinkedHashMap<>();
52 private Composite scrolledCmpBody;
53
54 public Control createUi(Composite parent, Path basePath) {
55 if (basePath == null)
56 throw new IllegalArgumentException("Context cannot be null");
57 parent.setLayout(new GridLayout());
58
59 // top filter
60 Composite filterCmp = new Composite(parent, SWT.NO_FOCUS);
61 filterCmp.setLayoutData(EclipseUiUtils.fillWidth());
62 addFilterPanel(filterCmp);
63
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 });
71
72 createBrowserPart(leftCmp, basePath);
73 // leftCmp.addControlListener(new ControlAdapter() {
74 // @Override
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),
79 // SWT.DEFAULT);
80 // // scrolledCmp.setMinSize(scrolledCmpBody.computeSize(SWT.DEFAULT,
81 // // r.height));
82 // }
83 // });
84
85 populateCurrEditedDisplay(displayBoxCmp, basePath);
86
87 // INIT
88 setEdited(basePath);
89 initialPath = basePath;
90 // form.layout(true, true);
91 return parent;
92 }
93
94 private void createBrowserPart(Composite parent, Path context) {
95 parent.setLayout(EclipseUiUtils.noSpaceGridLayout());
96
97 // scrolled composite
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);
103
104 scrolledCmpBody = new Composite(scrolledCmp, SWT.NO_FOCUS);
105 scrolledCmp.setContent(scrolledCmpBody);
106 scrolledCmpBody.addControlListener(new ControlAdapter() {
107 private static final long serialVersionUID = 183238447102854553L;
108
109 @Override
110 public void controlResized(ControlEvent e) {
111 Rectangle r = scrolledCmp.getClientArea();
112 scrolledCmp.setMinSize(scrolledCmpBody.computeSize(SWT.DEFAULT, r.height));
113 }
114 });
115 initExplorer(scrolledCmpBody, context);
116 scrolledCmpBody.layout(true, true);
117 scrolledCmp.layout();
118
119 }
120
121 private Control initExplorer(Composite parent, Path context) {
122 parent.setLayout(EclipseUiUtils.noSpaceGridLayout());
123 return createBrowserColumn(parent, context);
124 }
125
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);
134 return table;
135 }
136
137 public void addFilterPanel(Composite parent) {
138 parent.setLayout(EclipseUiUtils.noSpaceGridLayout(new GridLayout(2, false)));
139
140 parentPathTxt = new Text(parent, SWT.NO_FOCUS);
141 parentPathTxt.setEditable(false);
142
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;
148
149 public void modifyText(ModifyEvent event) {
150 modifyFilter(false);
151 }
152 });
153 filterTxt.addKeyListener(new KeyListener() {
154 private static final long serialVersionUID = 2533535233583035527L;
155
156 @Override
157 public void keyReleased(KeyEvent e) {
158 }
159
160 @Override
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())
168 currTable = table;
169 }
170
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();
182 e.doit = false;
183 }
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);
190 }
191 filterTxt.setFocus();
192 e.doit = false;
193 }
194 }
195 });
196 }
197
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) {
204 uniqueChild = entry;
205 } else {
206 moreThanOne = true;
207 break loop;
208 }
209 }
210 if (!moreThanOne)
211 return uniqueChild;
212 return null;
213 } catch (IOException ioe) {
214 throw new FsUiException(
215 "Unable to determine unique child existence and get it under " + parent + " with filter " + filter,
216 ioe);
217 }
218 }
219
220 private void setEdited(Path path) {
221 currEdited = path;
222 EclipseUiUtils.clear(displayBoxCmp);
223 populateCurrEditedDisplay(displayBoxCmp, currEdited);
224 refreshFilters(path);
225 refreshBrowser(path);
226 }
227
228 private void refreshFilters(Path path) {
229 parentPathTxt.setText(path.toUri().toString());
230 filterTxt.setText("");
231 filterTxt.getParent().layout();
232 }
233
234 private void refreshBrowser(Path currPath) {
235 Path currParPath = currPath.getParent();
236 Object[][] colMatrix = new Object[browserCols.size()][2];
237
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);
244 if (!leaveOpened)
245 lastLeftOpenedIndex = i;
246 }
247 if (currParPath.equals(path))
248 currPathIndex = i;
249 i++;
250 }
251
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]);
257 }
258 }
259
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);
266 }
267 }
268
269 if (!browserCols.containsKey(currPath) && Files.isDirectory(currPath))
270 createBrowserColumn(scrolledCmpBody, currPath);
271
272 scrolledCmpBody.setLayout(EclipseUiUtils.noSpaceGridLayout(new GridLayout(browserCols.size(), false)));
273 scrolledCmpBody.layout(true, true);
274 // also resize the scrolled composite
275 scrolledCmp.layout();
276 }
277
278 private void modifyFilter(boolean fromOutside) {
279 if (!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);
285 }
286 }
287
288 /**
289 * Recreates the content of the box that displays information about the current
290 * selected node.
291 */
292 private void populateCurrEditedDisplay(Composite parent, Path context) {
293 parent.setLayout(new GridLayout());
294
295 // if (isImg(context)) {
296 // EditableImage image = new Img(parent, RIGHT, context, imageWidth);
297 // image.setLayoutData(new GridData(SWT.CENTER, SWT.CENTER, true, false,
298 // 2, 1));
299 // }
300
301 try {
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");
309 } else {
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));
315 }
316 parent.layout(true, true);
317 } catch (IOException e) {
318 throw new FsUiException("Cannot display details for " + context, e);
319 }
320 }
321
322 private void addProperty(Composite parent, String propName, String value) {
323 Label contextL = new Label(parent, SWT.NONE);
324 contextL.setText(propName + ": " + value);
325 }
326
327 /**
328 * Almost canonical implementation of a table that displays the content of a
329 * directory
330 */
331 private class FilterEntitiesVirtualTable extends Composite {
332 private static final long serialVersionUID = 2223410043691844875L;
333
334 // Context
335 private Path context;
336 private Path currSelected = null;
337
338 // UI Objects
339 private FsTableViewer viewer;
340
341 @Override
342 public boolean setFocus() {
343 if (viewer.getTable().isDisposed())
344 return false;
345 if (currSelected != null)
346 viewer.setSelection(new StructuredSelection(currSelected), true);
347 else if (viewer.getSelection().isEmpty()) {
348 Object first = viewer.getElementAt(0);
349 if (first != null)
350 viewer.setSelection(new StructuredSelection(first), true);
351 }
352 return viewer.getTable().setFocus();
353 }
354
355 /**
356 * Enable highlighting the correct element in the table when externally browsing
357 * (typically via the command-line-like Text field)
358 */
359 void setSelected(Path selected) {
360 // to prevent change selection event to be thrown
361 currSelected = selected;
362 viewer.setSelection(new StructuredSelection(currSelected), true);
363 }
364
365 void filterList(String filter) {
366 viewer.setInput(context, filter);
367 }
368
369 public FilterEntitiesVirtualTable(Composite parent, int style, Path context) {
370 super(parent, SWT.NO_FOCUS);
371 this.context = context;
372 createTableViewer(this);
373 }
374
375 private void createTableViewer(final Composite parent) {
376 parent.setLayout(EclipseUiUtils.noSpaceGridLayout());
377
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 |
386 // SWT.V_SCROLL);
387 // Table table = viewer.getTable();
388 // table.setLayoutData(EclipseUiUtils.fillAll());
389
390 viewer = new FsTableViewer(parent, SWT.MULTI);
391 Table table = viewer.configureDefaultSingleColumnTable(COLUMN_WIDTH);
392
393 viewer.addSelectionChangedListener(new ISelectionChangedListener() {
394
395 @Override
396 public void selectionChanged(SelectionChangedEvent event) {
397 IStructuredSelection selection = (IStructuredSelection) viewer.getSelection();
398 if (selection.isEmpty())
399 return;
400 Object obj = selection.getFirstElement();
401 Path newSelected;
402 if (obj instanceof Path)
403 newSelected = (Path) obj;
404 else if (obj instanceof ParentDir)
405 newSelected = ((ParentDir) obj).getPath();
406 else
407 return;
408 if (newSelected.equals(currSelected))
409 return;
410 currSelected = newSelected;
411 setEdited(newSelected);
412
413 }
414 });
415
416 table.addKeyListener(new KeyListener() {
417 private static final long serialVersionUID = -8083424284436715709L;
418
419 @Override
420 public void keyReleased(KeyEvent e) {
421 }
422
423 @Override
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))
431 return;
432 if (selected != null) {
433 setEdited(selected);
434 browserCols.get(selected).setFocus();
435 }
436 } else if (e.keyCode == SWT.ARROW_LEFT) {
437 if (context.equals(initialPath))
438 return;
439 Path parent = context.getParent();
440 if (parent == null)
441 return;
442
443 setEdited(parent);
444 browserCols.get(parent).setFocus();
445 }
446 }
447 });
448 }
449 }
450 }