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