sticky documentation popup [take 1]
[fedora-idea.git] / platform / platform-impl / src / com / intellij / ide / CopyPasteManagerEx.java
blob6bc9d8e856075a5cd8a2b10d831b12fb1f1e739f
1 /*
2 * Copyright 2000-2009 JetBrains s.r.o.
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
16 package com.intellij.ide;
18 import com.intellij.Patches;
19 import com.intellij.ide.ui.UISettings;
20 import com.intellij.openapi.Disposable;
21 import com.intellij.openapi.application.ApplicationManager;
22 import com.intellij.openapi.application.ApplicationNamesInfo;
23 import com.intellij.openapi.diagnostic.Logger;
24 import com.intellij.openapi.extensions.Extensions;
25 import com.intellij.openapi.ide.CopyPasteManager;
26 import com.intellij.openapi.ide.CutElementMarker;
27 import com.intellij.openapi.ui.Messages;
28 import com.intellij.openapi.util.Comparing;
29 import com.intellij.util.EventDispatcher;
31 import java.awt.*;
32 import java.awt.datatransfer.*;
33 import java.io.IOException;
34 import java.util.ArrayList;
35 import java.util.concurrent.ExecutionException;
36 import java.util.concurrent.Future;
37 import java.util.concurrent.TimeUnit;
38 import java.util.concurrent.TimeoutException;
40 public class CopyPasteManagerEx extends CopyPasteManager implements ClipboardOwner {
41 private static final Logger LOG = Logger.getInstance("#com.intellij.ide.CopyPasteManagerEx");
43 private final ArrayList<Transferable> myDatas;
45 // private static long ourWastedMemory = 0;
46 // private static long ourLastPrintedMemory = 0;
47 // private static long ourLastPrintTime = 0;
48 // private static long ourInvokationCounter = 0;
50 private final EventDispatcher<ContentChangedListener> myDispatcher = EventDispatcher.create(ContentChangedListener.class);
51 private static final int DELAY_UNTIL_ABORT_CLIPBOARD_ACCESS = 2000;
52 private boolean myIsWarningShown = false;
54 public CopyPasteManagerEx() {
55 myDatas = new ArrayList<Transferable>();
58 public Transferable getSystemClipboardContents() {
59 final Transferable[] contents = new Transferable[] {null};
60 final boolean[] success = new boolean[] {false};
61 Runnable accessor = new Runnable() {
62 public void run() {
63 try {
64 for (int i = 0; i < 3; i++) {
65 try {
66 contents[0] = Toolkit.getDefaultToolkit().getSystemClipboard().getContents(CopyPasteManagerEx.this);
68 catch (IllegalStateException e) {
69 try {
70 Thread.sleep(50);
72 catch (InterruptedException e1) {
74 continue;
76 break;
79 success[0] = true;
81 catch (Throwable e) {
82 // No luck
84 finally {
85 Thread.interrupted(); // reset interrupted status
90 if (Patches.SUN_BUG_ID_4818143) {
91 final Future<?> accessorFuture = ApplicationManager.getApplication().executeOnPooledThread(accessor);
93 try {
94 accessorFuture.get(DELAY_UNTIL_ABORT_CLIPBOARD_ACCESS, TimeUnit.MILLISECONDS);
96 catch (InterruptedException e) {
97 // {no luck}
99 catch (TimeoutException e) {
100 // {no luck}
102 catch (ExecutionException e) {
103 LOG.error(e);
106 if (success[0]) return contents[0];
107 accessorFuture.cancel(true);
108 showWorkaroundMessage();
110 return null;
112 else {
113 accessor.run();
114 return contents[0];
118 private void showWorkaroundMessage() {
119 if (myIsWarningShown) return;
120 final String productName = ApplicationNamesInfo.getInstance().getProductName();
121 Messages.showErrorDialog(IdeBundle.message("error.paste.bug.workaround", productName, productName), IdeBundle.message("title.system.error"));
122 myIsWarningShown = true;
125 // private long getUsedMemory() {
126 // Runtime runtime = Runtime.getRuntime();
127 // long usedMemory = runtime.totalMemory() - runtime.freeMemory();
128 // return usedMemory;
129 // }
131 public void lostOwnership(Clipboard clipboard, Transferable contents) {
132 fireContentChanged(null);
135 void fireContentChanged(final Transferable oldTransferable) {
136 myDispatcher.getMulticaster().contentChanged(oldTransferable, getContents());
139 public void addContentChangedListener(ContentChangedListener listener) {
140 myDispatcher.addListener(listener);
143 public void addContentChangedListener(final ContentChangedListener listener, Disposable parentDisposable) {
144 myDispatcher.addListener(listener, parentDisposable);
147 public void removeContentChangedListener(ContentChangedListener listener) {
148 myDispatcher.removeListener(listener);
151 public void setContents(Transferable content) {
152 Transferable old = getContents();
153 addNewContentToStack(content);
155 setSystemClipboardContent(content);
157 fireContentChanged(old);
160 public boolean isCutElement(final Object element) {
161 for(CutElementMarker marker: Extensions.getExtensions(CutElementMarker.EP_NAME)) {
162 if (marker.isCutElement(element)) return true;
164 return false;
167 void setSystemClipboardContent(final Transferable content) {
168 final boolean[] success = new boolean[]{false};
169 final Runnable accessor = new Runnable() {
170 public void run() {
171 try {
172 for (int i = 0; i < 3; i++) {
173 try {
174 Toolkit.getDefaultToolkit().getSystemClipboard().setContents(content, CopyPasteManagerEx.this);
176 catch (IllegalStateException e) {
177 try {
178 Thread.sleep(50);
180 catch (InterruptedException e1) {
182 continue;
184 break;
186 success[0] = true;
188 finally {
189 Thread.interrupted(); // reset interrupted status
194 if (Patches.SUN_BUG_ID_4818143) {
195 Future<?> accessorFuture = ApplicationManager.getApplication().executeOnPooledThread(accessor);
197 try {
198 accessorFuture.get(DELAY_UNTIL_ABORT_CLIPBOARD_ACCESS, TimeUnit.MILLISECONDS);
200 catch (Exception e) { /* no luck */ }
202 if (!success[0]) {
203 showWorkaroundMessage();
204 accessorFuture.cancel(true);
207 else {
208 accessor.run();
212 private void addNewContentToStack(Transferable content) {
213 try {
214 String clipString = getStringContent(content);
215 if (clipString != null) {
216 Transferable same = null;
217 for (Transferable old : myDatas) {
218 if (clipString.equals(getStringContent(old))) {
219 same = old;
220 break;
224 if (same == null) {
225 myDatas.add(0, content);
226 deleteAfterAllowedMaximum();
228 else {
229 moveContentTopStackTop(same);
232 } catch (UnsupportedFlavorException e) {
233 } catch (IOException e) {
237 private static String getStringContent(Transferable content) throws UnsupportedFlavorException, IOException {
238 return (String) content.getTransferData(DataFlavor.stringFlavor);
241 private void deleteAfterAllowedMaximum() {
242 int max = UISettings.getInstance().MAX_CLIPBOARD_CONTENTS;
243 for (int i = myDatas.size() - 1; i >= max; i--) {
244 myDatas.remove(i);
248 public Transferable getContents() {
249 return getSystemClipboardContents();
252 public Transferable[] getAllContents() {
253 deleteAfterAllowedMaximum();
255 Transferable content = getSystemClipboardContents();
256 if (content != null) {
257 try {
258 String clipString = getStringContent(content);
259 String datasString = null;
261 if (!myDatas.isEmpty()) {
262 datasString = getStringContent(myDatas.get(0));
265 if (clipString != null && clipString.length() > 0 && !Comparing.equal(clipString, datasString)) {
266 myDatas.add(0, content);
268 } catch (UnsupportedFlavorException e) {
269 } catch (IOException e) {
273 return myDatas.toArray(new Transferable[myDatas.size()]);
276 public void removeContent(Transferable t) {
277 Transferable old = getContents();
278 boolean isCurrentClipboardContent = myDatas.indexOf(t) == 0;
279 myDatas.remove(t);
280 if (isCurrentClipboardContent) {
281 if (!myDatas.isEmpty()) {
282 setSystemClipboardContent(myDatas.get(0));
284 else {
285 setSystemClipboardContent(new StringSelection(""));
288 fireContentChanged(old);
291 public void moveContentTopStackTop(Transferable t) {
292 setSystemClipboardContent(t);
293 myDatas.remove(t);
294 myDatas.add(0, t);