View Javadoc
1   package org.argeo.documents.composites;
2   
3   import java.io.IOException;
4   import java.io.InputStream;
5   import java.nio.file.Files;
6   import java.nio.file.Path;
7   import java.nio.file.attribute.FileTime;
8   import java.text.DateFormat;
9   import java.text.SimpleDateFormat;
10  import java.util.ArrayList;
11  import java.util.Calendar;
12  import java.util.Date;
13  import java.util.List;
14  import java.util.Map;
15  
16  import javax.jcr.Node;
17  
18  import org.apache.commons.logging.Log;
19  import org.apache.commons.logging.LogFactory;
20  import org.argeo.cms.ui.fs.FileDrop;
21  import org.argeo.cms.ui.fs.FsStyles;
22  import org.argeo.cms.util.CmsUtils;
23  import org.argeo.connect.util.ConnectJcrUtils;
24  import org.argeo.connect.util.ConnectUtils;
25  import org.argeo.documents.DocumentsException;
26  import org.argeo.documents.DocumentsService;
27  import org.argeo.documents.ui.DocumentsUiService;
28  import org.argeo.eclipse.ui.ColumnDefinition;
29  import org.argeo.eclipse.ui.EclipseUiUtils;
30  import org.argeo.eclipse.ui.fs.FileIconNameLabelProvider;
31  import org.argeo.eclipse.ui.fs.FsTableViewer;
32  import org.argeo.eclipse.ui.fs.FsUiConstants;
33  import org.argeo.eclipse.ui.fs.FsUiUtils;
34  import org.argeo.eclipse.ui.fs.NioFileLabelProvider;
35  import org.argeo.eclipse.ui.fs.ParentDir;
36  import org.eclipse.jface.viewers.DoubleClickEvent;
37  import org.eclipse.jface.viewers.IDoubleClickListener;
38  import org.eclipse.jface.viewers.ISelectionChangedListener;
39  import org.eclipse.jface.viewers.IStructuredSelection;
40  import org.eclipse.jface.viewers.SelectionChangedEvent;
41  import org.eclipse.jface.viewers.Viewer;
42  import org.eclipse.swt.SWT;
43  import org.eclipse.swt.custom.SashForm;
44  import org.eclipse.swt.events.KeyEvent;
45  import org.eclipse.swt.events.KeyListener;
46  import org.eclipse.swt.events.ModifyEvent;
47  import org.eclipse.swt.events.ModifyListener;
48  import org.eclipse.swt.events.MouseAdapter;
49  import org.eclipse.swt.events.MouseEvent;
50  import org.eclipse.swt.events.SelectionAdapter;
51  import org.eclipse.swt.events.SelectionEvent;
52  import org.eclipse.swt.graphics.Point;
53  import org.eclipse.swt.layout.GridData;
54  import org.eclipse.swt.layout.GridLayout;
55  import org.eclipse.swt.layout.RowData;
56  import org.eclipse.swt.layout.RowLayout;
57  import org.eclipse.swt.widgets.Button;
58  import org.eclipse.swt.widgets.Composite;
59  import org.eclipse.swt.widgets.Control;
60  import org.eclipse.swt.widgets.Label;
61  import org.eclipse.swt.widgets.Table;
62  import org.eclipse.swt.widgets.Text;
63  
64  /**
65   * Default Documents folder composite: a sashForm layout with a simple table in
66   * the middle and an overview at right hand side.
67   */
68  public class DocumentsFolderComposite extends Composite {
69  	private final static Log log = LogFactory.getLog(DocumentsFolderComposite.class);
70  	private static final long serialVersionUID = -40347919096946585L;
71  
72  	private final Node currentBaseContext;
73  
74  	private final DocumentsService documentService;
75  	private final DocumentsUiService documentUiService = new DocumentsUiService();
76  
77  	// UI Parts for the browser
78  	private Composite filterCmp;
79  	private Composite breadCrumbCmp;
80  	private Text filterTxt;
81  	private FsTableViewer directoryDisplayViewer;
82  	private Composite rightPanelCmp;
83  
84  	private DocumentsContextMenu contextMenu;
85  	private DateFormat dateFormat = new SimpleDateFormat("YYYY-MM-dd HH:mm");
86  
87  	// Local context
88  	private Path initialPath;
89  	private Path currentFolder;
90  
91  	public DocumentsFolderComposite(Composite parent, int style, Node context, DocumentsService documentService) {
92  		super(parent, style);
93  		this.documentService = documentService;
94  		this.currentBaseContext = context;
95  
96  		this.setLayout(EclipseUiUtils.noSpaceGridLayout());
97  
98  		SashForm form = new SashForm(this, SWT.HORIZONTAL);
99  
100 		Composite centerCmp = new Composite(form, SWT.BORDER | SWT.NO_FOCUS);
101 		createDisplay(centerCmp);
102 
103 		rightPanelCmp = new Composite(form, SWT.NO_FOCUS);
104 
105 		form.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
106 		form.setWeights(new int[] { 55, 20 });
107 	}
108 
109 	public void populate(Path path) {
110 		initialPath = path;
111 		directoryDisplayViewer.setInitialPath(initialPath);
112 		setInput(path);
113 	}
114 
115 	void refresh() {
116 		modifyFilter(false);
117 	}
118 
119 	private void createDisplay(final Composite parent) {
120 		parent.setLayout(EclipseUiUtils.noSpaceGridLayout());
121 
122 		// top filter
123 		filterCmp = new Composite(parent, SWT.NO_FOCUS);
124 		filterCmp.setLayoutData(EclipseUiUtils.fillWidth());
125 		RowLayout rl = new RowLayout(SWT.HORIZONTAL);
126 		rl.wrap = true;
127 		rl.center = true;
128 		filterCmp.setLayout(rl);
129 		// addFilterPanel(filterCmp);
130 
131 		// Main display
132 		directoryDisplayViewer = new FsTableViewer(parent, SWT.MULTI);
133 		List<ColumnDefinition> colDefs = new ArrayList<>();
134 		colDefs.add(new ColumnDefinition(new FileIconNameLabelProvider(), " Name", 250));
135 		colDefs.add(new ColumnDefinition(new NioFileLabelProvider(FsUiConstants.PROPERTY_SIZE), "Size", 100));
136 //		colDefs.add(new ColumnDefinition(new NioFileLabelProvider(FsUiConstants.PROPERTY_TYPE), "Type", 150));
137 		colDefs.add(new ColumnDefinition(new NioFileLabelProvider(FsUiConstants.PROPERTY_LAST_MODIFIED),
138 				"Last modified", 400));
139 		final Table table = directoryDisplayViewer.configureDefaultTable(colDefs);
140 		table.setLayoutData(EclipseUiUtils.fillAll());
141 
142 		directoryDisplayViewer.addSelectionChangedListener(new ISelectionChangedListener() {
143 
144 			@Override
145 			public void selectionChanged(SelectionChangedEvent event) {
146 				IStructuredSelection selection = (IStructuredSelection) directoryDisplayViewer.getSelection();
147 				Path selected = null;
148 				if (selection.isEmpty())
149 					setSelected(null);
150 				else {
151 					Object o = selection.getFirstElement();
152 					if (o instanceof Path)
153 						selected = (Path) o;
154 					else if (o instanceof ParentDir)
155 						selected = ((ParentDir) o).getPath();
156 				}
157 				if (selected != null) {
158 					// TODO manage multiple selection
159 					setSelected(selected);
160 				}
161 			}
162 		});
163 
164 		directoryDisplayViewer.addDoubleClickListener(new IDoubleClickListener() {
165 			@Override
166 			public void doubleClick(DoubleClickEvent event) {
167 				IStructuredSelection selection = (IStructuredSelection) directoryDisplayViewer.getSelection();
168 				Path selected = null;
169 				if (!selection.isEmpty()) {
170 					Object o = selection.getFirstElement();
171 					if (o instanceof Path)
172 						selected = (Path) o;
173 					else if (o instanceof ParentDir)
174 						selected = ((ParentDir) o).getPath();
175 				}
176 				if (selected != null) {
177 					if (Files.isDirectory(selected))
178 						setInput(selected);
179 					else
180 						externalNavigateTo(selected);
181 				}
182 			}
183 		});
184 
185 		// The context menu
186 		contextMenu = new DocumentsContextMenu(this, documentService, documentUiService,
187 				ConnectJcrUtils.getRepository(currentBaseContext));
188 
189 		table.addMouseListener(new MouseAdapter() {
190 			private static final long serialVersionUID = 6737579410648595940L;
191 
192 			@Override
193 			public void mouseDown(MouseEvent e) {
194 				if (e.button == 3) {
195 					// contextMenu.setCurrFolderPath(currDisplayedFolder);
196 					contextMenu.show(table, new Point(e.x, e.y),
197 							(IStructuredSelection) directoryDisplayViewer.getSelection(), currentFolder);
198 				}
199 			}
200 		});
201 
202 		FileDrop fileDrop = new FileDrop() {
203 
204 			@Override
205 			protected void processFileUpload(InputStream in, String fileName, String contetnType) throws IOException {
206 				Path file = currentFolder.resolve(fileName);
207 				Files.copy(in, file);
208 				refresh();
209 			}
210 		};
211 		fileDrop.createDropTarget(directoryDisplayViewer.getTable());
212 	}
213 
214 	/**
215 	 * Overwrite to enable single sourcing between workbench and CMS navigation
216 	 */
217 	protected void externalNavigateTo(Path path) {
218 
219 	}
220 
221 	private void addPathElementBtn(Path path) {
222 		Button elemBtn = new Button(breadCrumbCmp, SWT.PUSH);
223 		String nameStr;
224 		if (path.toString().equals("/"))
225 			nameStr = "[jcr:root]";
226 		else
227 			nameStr = path.getFileName().toString();
228 //		elemBtn.setText(nameStr + " >> ");
229 		elemBtn.setText(nameStr);
230 		CmsUtils.style(elemBtn, FsStyles.BREAD_CRUMB_BTN);
231 		elemBtn.addSelectionListener(new SelectionAdapter() {
232 			private static final long serialVersionUID = -4103695476023480651L;
233 
234 			@Override
235 			public void widgetSelected(SelectionEvent e) {
236 				setInput(path);
237 			}
238 		});
239 	}
240 
241 	public void setInput(Path path) {
242 		if (path.equals(currentFolder))
243 			return;
244 		// below initial path
245 		if (!initialPath.equals(path) && initialPath.startsWith(path))
246 			return;
247 		currentFolder = path;
248 
249 		Path diff = initialPath.relativize(currentFolder);
250 
251 		for (Control child : filterCmp.getChildren())
252 			if (!child.equals(filterTxt))
253 				child.dispose();
254 
255 		// Bread crumbs
256 		breadCrumbCmp = new Composite(filterCmp, SWT.NO_FOCUS);
257 		CmsUtils.style(breadCrumbCmp, FsStyles.BREAD_CRUMB_BTN);
258 		RowLayout breadCrumbLayout = new RowLayout();
259 		breadCrumbLayout.spacing = 0;
260 		breadCrumbLayout.marginTop = 0;
261 		breadCrumbLayout.marginBottom = 0;
262 		breadCrumbLayout.marginRight = 0;
263 		breadCrumbLayout.marginLeft = 0;
264 		breadCrumbCmp.setLayout(breadCrumbLayout);
265 		addPathElementBtn(initialPath);
266 		Path currTarget = initialPath;
267 		if (!diff.toString().equals(""))
268 			for (Path pathElem : diff) {
269 				currTarget = currTarget.resolve(pathElem);
270 				addPathElementBtn(currTarget);
271 			}
272 
273 		if (filterTxt != null) {
274 			filterTxt.setText("");
275 			filterTxt.moveBelow(null);
276 		} else {
277 			modifyFilter(false);
278 		}
279 		setSelected(null);
280 		filterCmp.getParent().layout(true, true);
281 	}
282 
283 	private void setSelected(Path path) {
284 		if (path == null)
285 			setOverviewInput(currentFolder);
286 		else
287 			setOverviewInput(path);
288 	}
289 
290 	public Viewer getViewer() {
291 		return directoryDisplayViewer;
292 	}
293 
294 	/**
295 	 * Recreates the content of the box that displays information about the current
296 	 * selected Path.
297 	 */
298 	private void setOverviewInput(Path path) {
299 		try {
300 			EclipseUiUtils.clear(rightPanelCmp);
301 			rightPanelCmp.setLayout(new GridLayout());
302 			if (path != null) {
303 				// if (isImg(context)) {
304 				// EditableImage image = new Img(parent, RIGHT, context,
305 				// imageWidth);
306 				// image.setLayoutData(new GridData(SWT.CENTER, SWT.CENTER,
307 				// true, false,
308 				// 2, 1));
309 				// }
310 
311 				Label contextL = new Label(rightPanelCmp, SWT.NONE);
312 				contextL.setText(path.getFileName().toString());
313 				contextL.setFont(EclipseUiUtils.getBoldFont(rightPanelCmp));
314 				FileTime lastModified = Files.getLastModifiedTime(path);
315 				if (lastModified.toMillis() != 0)
316 					try {
317 						String lastModifiedStr = dateFormat.format(new Date(lastModified.toMillis()));
318 						addProperty(rightPanelCmp, "Last modified", lastModifiedStr);
319 					} catch (Exception e) {
320 						log.error("Workarounded issue while getting last update date for " + path, e);
321 						addProperty(rightPanelCmp, "Last modified", "-");
322 					}
323 				// addProperty(rightPannelCmp, "Owner",
324 				// Files.getOwner(path).getName());
325 				if (Files.isDirectory(path)) {
326 					addProperty(rightPanelCmp, "Type", "Folder");
327 				} else {
328 					String mimeType = Files.probeContentType(path);
329 					if (EclipseUiUtils.isEmpty(mimeType))
330 						mimeType = "<i>Unknown</i>";
331 					addProperty(rightPanelCmp, "Type", mimeType);
332 					addProperty(rightPanelCmp, "Size", FsUiUtils.humanReadableByteCount(Files.size(path), false));
333 				}
334 
335 				// read all attributes
336 //				Map<String, Object> attrs = Files.readAttributes(path, "*");
337 //				for (String attr : attrs.keySet()) {
338 //					Object value = attrs.get(attr);
339 //					String str;
340 //					if (value instanceof Calendar) {
341 //						str = dateFormat.format(((Calendar) value).getTime());
342 //					} else {
343 //						str = value.toString();
344 //					}
345 //					addProperty(rightPanelCmp, attr, str);
346 //
347 //				}
348 			}
349 			rightPanelCmp.layout(true, true);
350 		} catch (IOException e) {
351 			throw new DocumentsException("Cannot display details for " + path.toString(), e);
352 		}
353 	}
354 
355 	private void addFilterPanel(Composite parent) {
356 		// parent.setLayout(EclipseUiUtils.noSpaceGridLayout(new GridLayout(2,
357 		// false)));
358 
359 		filterTxt = new Text(parent, SWT.SEARCH | SWT.ICON_CANCEL);
360 		filterTxt.setMessage("Search current folder");
361 		filterTxt.setLayoutData(new RowData(250, SWT.DEFAULT));
362 		filterTxt.addModifyListener(new ModifyListener() {
363 			private static final long serialVersionUID = 1L;
364 
365 			public void modifyText(ModifyEvent event) {
366 				modifyFilter(false);
367 			}
368 		});
369 		filterTxt.addKeyListener(new KeyListener() {
370 			private static final long serialVersionUID = 2533535233583035527L;
371 
372 			@Override
373 			public void keyReleased(KeyEvent e) {
374 			}
375 
376 			@Override
377 			public void keyPressed(KeyEvent e) {
378 				// boolean shiftPressed = (e.stateMask & SWT.SHIFT) != 0;
379 				// // boolean altPressed = (e.stateMask & SWT.ALT) != 0;
380 				// FilterEntitiesVirtualTable currTable = null;
381 				// if (currEdited != null) {
382 				// FilterEntitiesVirtualTable table =
383 				// browserCols.get(currEdited);
384 				// if (table != null && !table.isDisposed())
385 				// currTable = table;
386 				// }
387 				//
388 				// if (e.keyCode == SWT.ARROW_DOWN)
389 				// currTable.setFocus();
390 				// else if (e.keyCode == SWT.BS) {
391 				// if (filterTxt.getText().equals("")
392 				// && !(currEdited.getNameCount() == 1 ||
393 				// currEdited.equals(initialPath))) {
394 				// Path oldEdited = currEdited;
395 				// Path parentPath = currEdited.getParent();
396 				// setEdited(parentPath);
397 				// if (browserCols.containsKey(parentPath))
398 				// browserCols.get(parentPath).setSelected(oldEdited);
399 				// filterTxt.setFocus();
400 				// e.doit = false;
401 				// }
402 				// } else if (e.keyCode == SWT.TAB && !shiftPressed) {
403 				// Path uniqueChild = getOnlyChild(currEdited,
404 				// filterTxt.getText());
405 				// if (uniqueChild != null) {
406 				// // Highlight the unique chosen child
407 				// currTable.setSelected(uniqueChild);
408 				// setEdited(uniqueChild);
409 				// }
410 				// filterTxt.setFocus();
411 				// e.doit = false;
412 				// }
413 			}
414 		});
415 	}
416 
417 	// private Path getOnlyChild(Path parent, String filter) {
418 	// try (DirectoryStream<Path> stream =
419 	// Files.newDirectoryStream(currDisplayedFolder, filter + "*")) {
420 	// Path uniqueChild = null;
421 	// boolean moreThanOne = false;
422 	// loop: for (Path entry : stream) {
423 	// if (uniqueChild == null) {
424 	// uniqueChild = entry;
425 	// } else {
426 	// moreThanOne = true;
427 	// break loop;
428 	// }
429 	// }
430 	// if (!moreThanOne)
431 	// return uniqueChild;
432 	// return null;
433 	// } catch (IOException ioe) {
434 	// throw new DocumentsException(
435 	// "Unable to determine unique child existence and get it under " + parent +
436 	// " with filter " + filter,
437 	// ioe);
438 	// }
439 	// }
440 
441 	private void modifyFilter(boolean fromOutside) {
442 		if (!fromOutside)
443 			if (currentFolder != null) {
444 				String filter;
445 				if (filterTxt != null)
446 					filter = filterTxt.getText() + "*";
447 				else
448 					filter = "*";
449 				directoryDisplayViewer.setInput(currentFolder, filter);
450 			}
451 	}
452 
453 	// Simplify UI implementation
454 	private void addProperty(Composite parent, String propName, String value) {
455 		Label propLbl = new Label(parent, SWT.NONE);
456 		propLbl.setText(ConnectUtils.replaceAmpersand(propName + ": " + value));
457 		CmsUtils.markup(propLbl);
458 	}
459 
460 	public Path getCurrentFolder() {
461 		return currentFolder;
462 	}
463 
464 }