]> git.argeo.org Git - lgpl/argeo-commons.git/blob - FsContextMenu.java
f45629b04278900acb7bdf1b6237bce83c85ee1c
[lgpl/argeo-commons.git] / FsContextMenu.java
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 }