1 /*******************************************************************************
2  *  Copyright (c) 2000, 2013 IBM Corporation and others.
3  *
4  *  This program and the accompanying materials
5  *  are made available under the terms of the Eclipse Public License 2.0
6  *  which accompanies this distribution, and is available at
7  *  https://www.eclipse.org/legal/epl-2.0/
8  *
9  *  SPDX-License-Identifier: EPL-2.0
10  *
11  *  Contributors:
12  *     IBM Corporation - initial API and implementation
13  *     Wind River Systems - refactored on top of VirtualTreeModelViewer
14  *******************************************************************************/
15 package org.eclipse.debug.internal.ui.viewers.model;
16 
17 
18 import java.lang.reflect.InvocationTargetException;
19 import java.util.Collections;
20 import java.util.HashSet;
21 import java.util.Set;
22 
23 import org.eclipse.core.runtime.IProgressMonitor;
24 import org.eclipse.debug.internal.core.IInternalDebugCoreConstants;
25 import org.eclipse.debug.internal.ui.DebugUIPlugin;
26 import org.eclipse.debug.internal.ui.actions.AbstractDebugActionDelegate;
27 import org.eclipse.debug.internal.ui.actions.ActionMessages;
28 import org.eclipse.debug.internal.ui.viewers.model.provisional.ILabelUpdate;
29 import org.eclipse.debug.internal.ui.viewers.model.provisional.IModelDelta;
30 import org.eclipse.debug.internal.ui.viewers.model.provisional.IVirtualItemListener;
31 import org.eclipse.debug.internal.ui.viewers.model.provisional.IVirtualItemValidator;
32 import org.eclipse.debug.internal.ui.viewers.model.provisional.ModelDelta;
33 import org.eclipse.debug.internal.ui.viewers.model.provisional.TreeModelViewer;
34 import org.eclipse.debug.internal.ui.viewers.model.provisional.VirtualItem;
35 import org.eclipse.debug.internal.ui.viewers.model.provisional.VirtualItem.Index;
36 import org.eclipse.debug.internal.ui.viewers.model.provisional.VirtualTreeModelViewer;
37 import org.eclipse.debug.ui.IDebugView;
38 import org.eclipse.jface.action.IAction;
39 import org.eclipse.jface.dialogs.MessageDialog;
40 import org.eclipse.jface.dialogs.ProgressMonitorDialog;
41 import org.eclipse.jface.operation.IRunnableWithProgress;
42 import org.eclipse.jface.viewers.ISelection;
43 import org.eclipse.jface.viewers.IStructuredSelection;
44 import org.eclipse.jface.viewers.TreePath;
45 import org.eclipse.swt.SWT;
46 import org.eclipse.swt.SWTError;
47 import org.eclipse.swt.dnd.Clipboard;
48 import org.eclipse.swt.dnd.DND;
49 import org.eclipse.swt.dnd.TextTransfer;
50 import org.eclipse.swt.dnd.Transfer;
51 import org.eclipse.swt.widgets.Event;
52 import org.eclipse.swt.widgets.Tree;
53 import org.eclipse.swt.widgets.TreeItem;
54 
55 public class VirtualCopyToClipboardActionDelegate extends AbstractDebugActionDelegate {
56 
57 	private TreeModelViewer fClientViewer;
58 	private static final String TAB = "\t"; //$NON-NLS-1$
59 	private static final String SEPARATOR = "line.separator"; //$NON-NLS-1$
60 
61 	/**
62 	 * Virtual viewer listener.  It tracks progress of copy and increments
63 	 * the progress monitor.
64 	 */
65 	private class VirtualViewerListener implements ILabelUpdateListener, IVirtualItemListener {
66 
67 		VirtualTreeModelViewer fVirtualViewer;
68 		IProgressMonitor fProgressMonitor;
69 		int fSelectionRootDepth;
70 		Set<VirtualItem> fItemsToUpdate;
71 
72 		@Override
labelUpdateStarted(ILabelUpdate update)73 		public void labelUpdateStarted(ILabelUpdate update) {}
74 		@Override
labelUpdateComplete(ILabelUpdate update)75 		public void labelUpdateComplete(ILabelUpdate update) {
76 			VirtualItem updatedItem = fVirtualViewer.findItem(update.getElementPath());
77 			if (fItemsToUpdate.remove(updatedItem)) {
78 				incrementProgress(1);
79 			}
80 		}
81 		@Override
labelUpdatesBegin()82 		public void labelUpdatesBegin() {
83 		}
84 		@Override
labelUpdatesComplete()85 		public void labelUpdatesComplete() {
86 		}
87 
88 		@Override
revealed(VirtualItem item)89 		public void revealed(VirtualItem item) {
90 		}
91 
92 		@Override
disposed(VirtualItem item)93 		public void disposed(VirtualItem item) {
94 			if (fItemsToUpdate.remove(item)) {
95 				incrementProgress(1);
96 			}
97 		}
98 
incrementProgress(int count)99 		private void incrementProgress(int count) {
100 			IProgressMonitor pm;
101 			synchronized (VirtualCopyToClipboardActionDelegate.this) {
102 				pm = fProgressMonitor;
103 			}
104 			if (pm != null) {
105 				pm.worked(count);
106 				if (fItemsToUpdate.isEmpty()) {
107 					pm.done();
108 				}
109 			}
110 		}
111 	}
112 
113 	/**
114 	 * @see AbstractDebugActionDelegate#initialize(IAction, ISelection)
115 	 */
116 	@Override
initialize(IAction action, ISelection selection)117 	protected boolean initialize(IAction action, ISelection selection) {
118 		if (!isInitialized()) {
119 			IDebugView adapter= getView().getAdapter(IDebugView.class);
120 			if (adapter != null) {
121 				if (adapter.getViewer() instanceof TreeModelViewer) {
122 					setViewer((TreeModelViewer) adapter.getViewer());
123 				}
124 				adapter.setAction(getActionId(), action);
125 			}
126 			return super.initialize(action, selection);
127 		}
128 		return false;
129 	}
130 
getActionId()131 	protected String getActionId() {
132 		return IDebugView.COPY_ACTION;
133 	}
134 
135 	/**
136 	 * Appends the representation of the specified element (using the label provider and indent)
137 	 * to the buffer.  For elements down to stack frames, children representations
138 	 * are append to the buffer as well.
139 	 * @param item Item to append to string
140 	 * @param buffer String buffer for copy text.
141 	 * @param indent Current indentation in tree text.
142 	 */
append(VirtualItem item, StringBuilder buffer, int indent)143 	protected void append(VirtualItem item, StringBuilder buffer, int indent) {
144 		for (int i= 0; i < indent; i++) {
145 			buffer.append(TAB);
146 		}
147 		String[] labels = (String[]) item.getData(VirtualItem.LABEL_KEY);
148 		if(labels != null && labels.length > 0) {
149 			for (String label : labels) {
150 				String text = trimLabel(label);
151 				if (text != null && !text.equals(IInternalDebugCoreConstants.EMPTY_STRING)) {
152 					buffer.append(text+TAB);
153 				}
154 			}
155 			buffer.append(System.getProperty(SEPARATOR));
156 		}
157 	}
158 
159 	/**
160 	 * Trims the given String. Subclasses might want to cut off additional
161 	 * things from the given label retrieved by the label provider by overriding
162 	 * this method.
163 	 *
164 	 * @param label the label that should be trimmed (might be null)
165 	 * @return the trimmed label or null if label is null
166 	 */
trimLabel(String label)167 	protected String trimLabel(String label) {
168 		if (label == null) {
169 			return null;
170 		}
171 		return label.trim();
172 	}
173 
174 	private class ItemsToCopyVirtualItemValidator implements IVirtualItemValidator {
175 
176 		Set<VirtualItem> fItemsToCopy = Collections.EMPTY_SET;
177 		Set<VirtualItem> fItemsToValidate = Collections.EMPTY_SET;
178 
179 		@Override
isItemVisible(VirtualItem item)180 		public boolean isItemVisible(VirtualItem item) {
181 			return fItemsToValidate.contains(item);
182 		}
183 
184 		@Override
showItem(VirtualItem item)185 		public void showItem(VirtualItem item) {
186 		}
187 
setItemsToCopy(Set<VirtualItem> itemsToCopy)188 		void setItemsToCopy(Set<VirtualItem> itemsToCopy) {
189 			fItemsToCopy = itemsToCopy;
190 			fItemsToValidate = new HashSet<>();
191 			for (VirtualItem itemToCopy : itemsToCopy) {
192 				while (itemToCopy != null) {
193 					fItemsToValidate.add(itemToCopy);
194 					itemToCopy = itemToCopy.getParent();
195 				}
196 			}
197 		}
198 	}
199 
initVirtualViewer(TreeModelViewer clientViewer, VirtualViewerListener listener, ItemsToCopyVirtualItemValidator validator)200 	private VirtualTreeModelViewer initVirtualViewer(TreeModelViewer clientViewer, VirtualViewerListener listener, ItemsToCopyVirtualItemValidator validator) {
201 		Object input = clientViewer.getInput();
202 		ModelDelta stateDelta = new ModelDelta(input, IModelDelta.NO_CHANGE);
203 		clientViewer.saveElementState(TreePath.EMPTY, stateDelta, IModelDelta.EXPAND);
204 		VirtualTreeModelViewer virtualViewer = new VirtualTreeModelViewer(
205 			clientViewer.getDisplay(),
206 			SWT.VIRTUAL,
207 			clientViewer.getPresentationContext(),
208 			validator);
209 		virtualViewer.setFilters(clientViewer.getFilters());
210 		virtualViewer.addLabelUpdateListener(listener);
211 		virtualViewer.getTree().addItemListener(listener);
212 		String[] columns = clientViewer.getPresentationContext().getColumns();
213 		virtualViewer.setInput(input);
214 		if (virtualViewer.canToggleColumns()) {
215 			virtualViewer.setShowColumns(clientViewer.isShowColumns());
216 			virtualViewer.setVisibleColumns(columns);
217 		}
218 		virtualViewer.updateViewer(stateDelta);
219 
220 		// Parse selected items from client viewer and add them to the virtual viewer selection.
221 		listener.fSelectionRootDepth = Integer.MAX_VALUE;
222 		TreeItem[] selection = getSelectedItems(clientViewer);
223 		Set<VirtualItem> vSelection = new HashSet<>(selection.length * 4 / 3);
224 		for (TreeItem element : selection) {
225 			TreePath parentPath = fClientViewer.getTreePathFromItem(element.getParentItem());
226 			listener.fSelectionRootDepth = Math.min(parentPath.getSegmentCount() + 1, listener.fSelectionRootDepth);
227 			VirtualItem parentVItem = virtualViewer.findItem(parentPath);
228 			if (parentVItem != null) {
229 				int index = -1;
230 				TreeItem parentItem = element.getParentItem();
231 				if (parentItem != null) {
232 					index = parentItem.indexOf(element);
233 				} else {
234 					Tree parentTree = element.getParent();
235 					index = parentTree.indexOf(element);
236 				}
237 				index = ((ITreeModelContentProvider)clientViewer.getContentProvider()).viewToModelIndex(parentPath, index);
238 				vSelection.add( parentVItem.getItem(new Index(index)) );
239 			}
240 		}
241 		validator.setItemsToCopy(vSelection);
242 		listener.fItemsToUpdate = new HashSet<>(vSelection);
243 		virtualViewer.getTree().validate();
244 		return virtualViewer;
245 	}
246 
getSelectedItems(TreeModelViewer clientViewer)247 	protected TreeItem[] getSelectedItems(TreeModelViewer clientViewer) {
248 		return clientViewer.getTree().getSelection();
249 	}
250 
251 	/**
252 	 * Do the specific action using the current selection.
253 	 * @param action Action that is running.
254 	 */
255 	@Override
run(final IAction action)256 	public void run(final IAction action) {
257 		if (fClientViewer.getSelection().isEmpty()) {
258 			return;
259 		}
260 
261 		final VirtualViewerListener listener = new VirtualViewerListener();
262 		ItemsToCopyVirtualItemValidator validator = new ItemsToCopyVirtualItemValidator();
263 		VirtualTreeModelViewer virtualViewer = initVirtualViewer(fClientViewer, listener, validator);
264 		listener.fVirtualViewer = virtualViewer;
265 
266 		ProgressMonitorDialog dialog = new TimeTriggeredProgressMonitorDialog(fClientViewer.getControl().getShell(), 500);
267 		final IProgressMonitor monitor = dialog.getProgressMonitor();
268 		dialog.setCancelable(true);
269 
270 		IRunnableWithProgress runnable = new IRunnableWithProgress() {
271 			@Override
272 			public void run(final IProgressMonitor m) throws InvocationTargetException, InterruptedException {
273 				synchronized(listener) {
274 					listener.fProgressMonitor = m;
275 					listener.fProgressMonitor.beginTask(DebugUIPlugin.removeAccelerators(getAction().getText()), listener.fItemsToUpdate.size());
276 				}
277 
278 				while (!listener.fItemsToUpdate.isEmpty() && !listener.fProgressMonitor.isCanceled()) {
279 					Thread.sleep(1);
280 				}
281 				synchronized(listener) {
282 					listener.fProgressMonitor = null;
283 				}
284 			}
285 		};
286 		try {
287 			dialog.run(true, true, runnable);
288 		} catch (InvocationTargetException e) {
289 			DebugUIPlugin.log(e);
290 			return;
291 		} catch (InterruptedException e) {
292 			return;
293 		}
294 
295 		if (!monitor.isCanceled()) {
296 			copySelectionToClipboard(virtualViewer, validator.fItemsToCopy, listener.fSelectionRootDepth);
297 		}
298 
299 		virtualViewer.removeLabelUpdateListener(listener);
300 		virtualViewer.getTree().removeItemListener(listener);
301 		virtualViewer.dispose();
302 	}
303 
copySelectionToClipboard(VirtualTreeModelViewer virtualViewer, Set<VirtualItem> itemsToCopy, int selectionRootDepth)304 	private void copySelectionToClipboard(VirtualTreeModelViewer virtualViewer, Set<VirtualItem> itemsToCopy, int selectionRootDepth) {
305 		StringBuilder buffer = new StringBuilder();
306 		writeItemToBuffer (virtualViewer.getTree(), itemsToCopy, buffer, -selectionRootDepth);
307 		writeBufferToClipboard(buffer);
308 	}
309 
writeItemToBuffer(VirtualItem item, Set<VirtualItem> itemsToCopy, StringBuilder buffer, int indent)310 	protected void writeItemToBuffer(VirtualItem item, Set<VirtualItem> itemsToCopy, StringBuilder buffer, int indent) {
311 		if (itemsToCopy.contains(item)) {
312 			append(item, buffer, indent);
313 		}
314 		VirtualItem[] children = item.getItems();
315 		if (children != null) {
316 			for (VirtualItem element : children) {
317 				writeItemToBuffer(element, itemsToCopy, buffer, indent + 1);
318 			}
319 		}
320 	}
321 
writeBufferToClipboard(StringBuilder buffer)322 	protected void writeBufferToClipboard(StringBuilder buffer) {
323 		if (buffer.length() == 0) {
324 			return;
325 		}
326 
327 		TextTransfer plainTextTransfer = TextTransfer.getInstance();
328 		Clipboard clipboard= new Clipboard(fClientViewer.getControl().getDisplay());
329 		try {
330 			clipboard.setContents(
331 					new String[]{buffer.toString()},
332 					new Transfer[]{plainTextTransfer});
333 		} catch (SWTError e){
334 			if (e.code != DND.ERROR_CANNOT_SET_CLIPBOARD) {
335 				throw e;
336 			}
337 			if (MessageDialog.openQuestion(fClientViewer.getControl().getShell(), ActionMessages.CopyToClipboardActionDelegate_Problem_Copying_to_Clipboard_1, ActionMessages.CopyToClipboardActionDelegate_There_was_a_problem_when_accessing_the_system_clipboard__Retry__2)) { //
338 				writeBufferToClipboard(buffer);
339 			}
340 		} finally {
341 			clipboard.dispose();
342 		}
343 	}
344 
getViewer()345 	protected TreeModelViewer getViewer() {
346 		return fClientViewer;
347 	}
348 
setViewer(TreeModelViewer viewer)349 	protected void setViewer(TreeModelViewer viewer) {
350 		fClientViewer = viewer;
351 	}
352 	/**
353 	 * @see AbstractDebugActionDelegate#doAction(Object)
354 	 */
355 	@Override
doAction(Object element)356 	protected void doAction(Object element) {
357 		//not used
358 	}
359 
360 	@Override
getEnableStateForSelection(IStructuredSelection selection)361 	protected boolean getEnableStateForSelection(IStructuredSelection selection) {
362 		if (selection.isEmpty()) {
363 			return true;
364 		} else {
365 			return super.getEnableStateForSelection(selection);
366 		}
367 	}
368 
369 	@Override
runWithEvent(IAction action, Event event)370 	public void runWithEvent(IAction action, Event event) {
371 		run(action);
372 	}
373 }
374