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