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