View Javadoc
1   package org.argeo.cms.ui.fs;
2   
3   import java.io.ByteArrayInputStream;
4   import java.io.IOException;
5   import java.io.InputStream;
6   import java.lang.reflect.Method;
7   import java.net.URI;
8   import java.nio.file.Files;
9   import java.nio.file.Path;
10  import java.nio.file.Paths;
11  import java.util.ArrayList;
12  import java.util.HashMap;
13  import java.util.Iterator;
14  import java.util.List;
15  import java.util.Map;
16  
17  import org.apache.commons.io.IOUtils;
18  import org.apache.commons.logging.Log;
19  import org.apache.commons.logging.LogFactory;
20  import org.argeo.cms.CmsException;
21  import org.argeo.cms.ui.util.CmsUiUtils;
22  import org.argeo.eclipse.ui.EclipseUiUtils;
23  import org.argeo.eclipse.ui.dialogs.SingleValue;
24  import org.eclipse.jface.dialogs.MessageDialog;
25  import org.eclipse.jface.viewers.IStructuredSelection;
26  import org.eclipse.swt.SWT;
27  import org.eclipse.swt.events.SelectionAdapter;
28  import org.eclipse.swt.events.SelectionEvent;
29  import org.eclipse.swt.events.ShellEvent;
30  import org.eclipse.swt.graphics.Point;
31  import org.eclipse.swt.layout.GridData;
32  import org.eclipse.swt.widgets.Button;
33  import org.eclipse.swt.widgets.Composite;
34  import org.eclipse.swt.widgets.Control;
35  import org.eclipse.swt.widgets.FileDialog;
36  import org.eclipse.swt.widgets.Label;
37  import org.eclipse.swt.widgets.Shell;
38  
39  /** Generic popup context menu to manage NIO Path in a Viewer. */
40  public class FsContextMenu extends Shell {
41  	private static final long serialVersionUID = -9120261153509855795L;
42  
43  	private final static Log log = LogFactory.getLog(FsContextMenu.class);
44  
45  	// Default known actions
46  	public final static String ACTION_ID_CREATE_FOLDER = "createFolder";
47  	public final static String ACTION_ID_BOOKMARK_FOLDER = "bookmarkFolder";
48  	public final static String ACTION_ID_SHARE_FOLDER = "shareFolder";
49  	public final static String ACTION_ID_DOWNLOAD_FOLDER = "downloadFolder";
50  	public final static String ACTION_ID_DELETE = "delete";
51  	public final static String ACTION_ID_UPLOAD_FILE = "uploadFiles";
52  	public final static String ACTION_ID_OPEN = "open";
53  
54  	// Local context
55  	private final CmsFsBrowser browser;
56  	// private final Viewer viewer;
57  	private final static String KEY_ACTION_ID = "actionId";
58  	private final static String[] DEFAULT_ACTIONS = { ACTION_ID_CREATE_FOLDER, ACTION_ID_BOOKMARK_FOLDER,
59  			ACTION_ID_SHARE_FOLDER, ACTION_ID_DOWNLOAD_FOLDER, ACTION_ID_DELETE, ACTION_ID_UPLOAD_FILE,
60  			ACTION_ID_OPEN };
61  	private Map<String, Button> actionButtons = new HashMap<String, Button>();
62  
63  	private Path currFolderPath;
64  
65  	public FsContextMenu(CmsFsBrowser browser) { // Viewer viewer, Display
66  													// display) {
67  		super(browser.getDisplay(), SWT.NO_TRIM | SWT.BORDER | SWT.ON_TOP);
68  		this.browser = browser;
69  		setLayout(EclipseUiUtils.noSpaceGridLayout());
70  
71  		Composite boxCmp = new Composite(this, SWT.NO_FOCUS | SWT.BORDER);
72  		boxCmp.setLayout(EclipseUiUtils.noSpaceGridLayout());
73  		CmsUiUtils.style(boxCmp, FsStyles.CONTEXT_MENU_BOX);
74  		createContextMenu(boxCmp);
75  
76  		addShellListener(new ActionsShellListener());
77  	}
78  
79  	protected void createContextMenu(Composite boxCmp) {
80  		ActionsSelListener asl = new ActionsSelListener();
81  		for (String actionId : DEFAULT_ACTIONS) {
82  			Button btn = new Button(boxCmp, SWT.FLAT | SWT.PUSH | SWT.LEAD);
83  			btn.setText(getLabel(actionId));
84  			btn.setLayoutData(EclipseUiUtils.fillWidth());
85  			CmsUiUtils.markup(btn);
86  			CmsUiUtils.style(btn, actionId + FsStyles.BUTTON_SUFFIX);
87  			btn.setData(KEY_ACTION_ID, actionId);
88  			btn.addSelectionListener(asl);
89  			actionButtons.put(actionId, btn);
90  		}
91  	}
92  
93  	protected String getLabel(String actionId) {
94  		switch (actionId) {
95  		case ACTION_ID_CREATE_FOLDER:
96  			return "Create Folder";
97  		case ACTION_ID_BOOKMARK_FOLDER:
98  			return "Bookmark Folder";
99  		case ACTION_ID_SHARE_FOLDER:
100 			return "Share Folder";
101 		case ACTION_ID_DOWNLOAD_FOLDER:
102 			return "Download as zip archive";
103 		case ACTION_ID_DELETE:
104 			return "Delete";
105 		case ACTION_ID_UPLOAD_FILE:
106 			return "Upload Files";
107 		case ACTION_ID_OPEN:
108 			return "Open";
109 		default:
110 			throw new IllegalArgumentException("Unknown action ID " + actionId);
111 		}
112 	}
113 
114 	protected void aboutToShow(Control source, Point location) {
115 		IStructuredSelection selection = ((IStructuredSelection) browser.getViewer().getSelection());
116 		boolean emptySel = true;
117 		boolean multiSel = false;
118 		boolean isFolder = true;
119 		if (selection != null && !selection.isEmpty()) {
120 			emptySel = false;
121 			multiSel = selection.size() > 1;
122 			if (!multiSel && selection.getFirstElement() instanceof Path) {
123 				isFolder = Files.isDirectory((Path) selection.getFirstElement());
124 			}
125 		}
126 		if (emptySel) {
127 			setVisible(true, ACTION_ID_CREATE_FOLDER, ACTION_ID_UPLOAD_FILE);
128 			setVisible(false, ACTION_ID_SHARE_FOLDER, ACTION_ID_DOWNLOAD_FOLDER, ACTION_ID_DELETE, ACTION_ID_OPEN,
129 					// to be implemented
130 					ACTION_ID_BOOKMARK_FOLDER);
131 		} else if (multiSel) {
132 			setVisible(true, ACTION_ID_CREATE_FOLDER, ACTION_ID_UPLOAD_FILE, ACTION_ID_DELETE);
133 			setVisible(false, ACTION_ID_SHARE_FOLDER, ACTION_ID_DOWNLOAD_FOLDER, ACTION_ID_OPEN,
134 					// to be implemented
135 					ACTION_ID_BOOKMARK_FOLDER);
136 		} else if (isFolder) {
137 			setVisible(true, ACTION_ID_CREATE_FOLDER, ACTION_ID_UPLOAD_FILE, ACTION_ID_DELETE);
138 			setVisible(false, ACTION_ID_OPEN,
139 					// to be implemented
140 					ACTION_ID_SHARE_FOLDER, ACTION_ID_DOWNLOAD_FOLDER, ACTION_ID_BOOKMARK_FOLDER);
141 		} else {
142 			setVisible(true, ACTION_ID_CREATE_FOLDER, ACTION_ID_UPLOAD_FILE, ACTION_ID_OPEN, ACTION_ID_DELETE);
143 			setVisible(false, ACTION_ID_SHARE_FOLDER, ACTION_ID_DOWNLOAD_FOLDER,
144 					// to be implemented
145 					ACTION_ID_BOOKMARK_FOLDER);
146 		}
147 	}
148 
149 	private void setVisible(boolean visible, String... buttonIds) {
150 		for (String id : buttonIds) {
151 			Button button = actionButtons.get(id);
152 			button.setVisible(visible);
153 			GridData gd = (GridData) button.getLayoutData();
154 			gd.heightHint = visible ? SWT.DEFAULT : 0;
155 		}
156 	}
157 
158 	public void show(Control source, Point location, Path currFolderPath) {
159 		if (isVisible())
160 			setVisible(false);
161 		// TODO find a better way to retrieve the parent path (cannot be deduced
162 		// from table content because it will fail on an empty folder)
163 		this.currFolderPath = currFolderPath;
164 		aboutToShow(source, location);
165 		pack();
166 		layout();
167 		if (source instanceof Control)
168 			setLocation(((Control) source).toDisplay(location.x, location.y));
169 		open();
170 	}
171 
172 	class StyleButton extends Label {
173 		private static final long serialVersionUID = 7731102609123946115L;
174 
175 		public StyleButton(Composite parent, int swtStyle) {
176 			super(parent, swtStyle);
177 		}
178 
179 	}
180 
181 	// class ActionsMouseListener extends MouseAdapter {
182 	// private static final long serialVersionUID = -1041871937815812149L;
183 	//
184 	// @Override
185 	// public void mouseDown(MouseEvent e) {
186 	// Object eventSource = e.getSource();
187 	// if (e.button == 1) {
188 	// if (eventSource instanceof Button) {
189 	// Button pressedBtn = (Button) eventSource;
190 	// String actionId = (String) pressedBtn.getData(KEY_ACTION_ID);
191 	// switch (actionId) {
192 	// case ACTION_ID_CREATE_FOLDER:
193 	// createFolder();
194 	// break;
195 	// case ACTION_ID_DELETE:
196 	// deleteItems();
197 	// break;
198 	// default:
199 	// throw new IllegalArgumentException("Unimplemented action " + actionId);
200 	// // case ACTION_ID_SHARE_FOLDER:
201 	// // return "Share Folder";
202 	// // case ACTION_ID_DOWNLOAD_FOLDER:
203 	// // return "Download as zip archive";
204 	// // case ACTION_ID_UPLOAD_FILE:
205 	// // return "Upload Files";
206 	// // case ACTION_ID_OPEN:
207 	// // return "Open";
208 	// }
209 	// }
210 	// }
211 	// viewer.getControl().setFocus();
212 	// // setVisible(false);
213 	// }
214 	// }
215 
216 	class ActionsSelListener extends SelectionAdapter {
217 		private static final long serialVersionUID = -1041871937815812149L;
218 
219 		@Override
220 		public void widgetSelected(SelectionEvent e) {
221 			Object eventSource = e.getSource();
222 			if (eventSource instanceof Button) {
223 				Button pressedBtn = (Button) eventSource;
224 				String actionId = (String) pressedBtn.getData(KEY_ACTION_ID);
225 				switch (actionId) {
226 				case ACTION_ID_CREATE_FOLDER:
227 					createFolder();
228 					break;
229 				case ACTION_ID_DELETE:
230 					deleteItems();
231 					break;
232 				case ACTION_ID_OPEN:
233 					openFile();
234 					break;
235 				case ACTION_ID_UPLOAD_FILE:
236 					uploadFiles();
237 					break;
238 				default:
239 					throw new IllegalArgumentException("Unimplemented action " + actionId);
240 					// case ACTION_ID_SHARE_FOLDER:
241 					// return "Share Folder";
242 					// case ACTION_ID_DOWNLOAD_FOLDER:
243 					// return "Download as zip archive";
244 					// case ACTION_ID_OPEN:
245 					// return "Open";
246 				}
247 			}
248 			browser.setFocus();
249 			// viewer.getControl().setFocus();
250 			// setVisible(false);
251 
252 		}
253 	}
254 
255 	class ActionsShellListener extends org.eclipse.swt.events.ShellAdapter {
256 		private static final long serialVersionUID = -5092341449523150827L;
257 
258 		@Override
259 		public void shellDeactivated(ShellEvent e) {
260 			setVisible(false);
261 		}
262 	}
263 
264 	private void openFile() {
265 		log.warn("Implement single sourced, workbench independant \"Open File\" action");
266 	}
267 
268 	private void deleteItems() {
269 		IStructuredSelection selection = ((IStructuredSelection) browser.getViewer().getSelection());
270 		if (selection.isEmpty())
271 			return;
272 
273 		StringBuilder builder = new StringBuilder();
274 		@SuppressWarnings("unchecked")
275 		Iterator<Object> iterator = selection.iterator();
276 		List<Path> paths = new ArrayList<>();
277 
278 		while (iterator.hasNext()) {
279 			Path path = (Path) iterator.next();
280 			builder.append(path.getFileName() + ", ");
281 			paths.add(path);
282 		}
283 		String msg = "You are about to delete following elements: " + builder.substring(0, builder.length() - 2)
284 				+ ". Are you sure?";
285 		if (MessageDialog.openConfirm(this, "Confirm deletion", msg)) {
286 			for (Path path : paths) {
287 				try {
288 					// Might have already been deleted if we are in a tree
289 					Files.deleteIfExists(path);
290 				} catch (IOException e) {
291 					throw new CmsException("Cannot delete path " + path, e);
292 				}
293 			}
294 			browser.refresh();
295 		}
296 	}
297 
298 	private void createFolder() {
299 		String msg = "Please provide a name.";
300 		String name = SingleValue.ask("Create folder", msg);
301 		// TODO enhance check of name validity
302 		if (EclipseUiUtils.notEmpty(name)) {
303 			try {
304 				Path child = currFolderPath.resolve(name);
305 				if (Files.exists(child))
306 					throw new CmsException("An item with name " + name + " already exists at "
307 							+ currFolderPath.toString() + ", cannot create");
308 				else
309 					Files.createDirectories(child);
310 				browser.refresh();
311 			} catch (IOException e) {
312 				throw new CmsException("Cannot create folder " + name + " at " + currFolderPath.toString(), e);
313 			}
314 		}
315 	}
316 
317 	private void uploadFiles() {
318 		try {
319 			FileDialog dialog = new FileDialog(browser.getShell(), SWT.MULTI);
320 			dialog.setText("Choose one or more files to upload");
321 
322 			if (EclipseUiUtils.notEmpty(dialog.open())) {
323 				String[] names = dialog.getFileNames();
324 				// Workaround small differences between RAP and RCP
325 				// 1. returned names are absolute path on RAP and
326 				// relative in RCP
327 				// 2. in RCP we must use getFilterPath that does not
328 				// exists on RAP
329 				Method filterMethod = null;
330 				Path parPath = null;
331 				try {
332 					filterMethod = dialog.getClass().getDeclaredMethod("getFilterPath");
333 					String filterPath = (String) filterMethod.invoke(dialog);
334 					parPath = Paths.get(filterPath);
335 				} catch (NoSuchMethodException nsme) { // RAP
336 				}
337 				if (names.length == 0)
338 					return;
339 				else {
340 					loop: for (String name : names) {
341 						Path tmpPath = Paths.get(name);
342 						if (parPath != null)
343 							tmpPath = parPath.resolve(tmpPath);
344 						if (Files.exists(tmpPath)) {
345 							URI uri = tmpPath.toUri();
346 							String uriStr = uri.toString();
347 
348 							if (Files.isDirectory(tmpPath)) {
349 								MessageDialog.openError(browser.getShell(), "Unimplemented directory import",
350 										"Upload of directories in the system is not yet implemented");
351 								continue loop;
352 							}
353 							Path targetPath = currFolderPath.resolve(tmpPath.getFileName().toString());
354 							InputStream in = null;
355 							try {
356 								in = new ByteArrayInputStream(Files.readAllBytes(tmpPath));
357 								Files.copy(in, targetPath);
358 								Files.delete(tmpPath);
359 							} finally {
360 								IOUtils.closeQuietly(in);
361 							}
362 							if (log.isDebugEnabled())
363 								log.debug("copied uploaded file " + uriStr + " to " + targetPath.toString());
364 						} else {
365 							String msg = "Cannot copy tmp file from " + tmpPath.toString();
366 							if (parPath != null)
367 								msg += "\nPlease remember that file upload fails when choosing files from the \"Recently Used\" bookmarks on some OS";
368 							MessageDialog.openError(browser.getShell(), "Missing file", msg);
369 							continue loop;
370 						}
371 					}
372 					browser.refresh();
373 				}
374 			}
375 		} catch (Exception e) {
376 			e.printStackTrace();
377 			MessageDialog.openError(getShell(), "Upload has failed", "Cannot import files to " + currFolderPath);
378 		}
379 	}
380 
381 	public void setCurrFolderPath(Path currFolderPath) {
382 		this.currFolderPath = currFolderPath;
383 	}
384 }