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