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
;
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() {
64 for (int i
= 0; i
< 3; i
++) {
66 contents
[0] = Toolkit
.getDefaultToolkit().getSystemClipboard().getContents(CopyPasteManagerEx
.this);
68 catch (IllegalStateException e
) {
72 catch (InterruptedException e1
) {
85 Thread
.interrupted(); // reset interrupted status
90 if (Patches
.SUN_BUG_ID_4818143
) {
91 final Future
<?
> accessorFuture
= ApplicationManager
.getApplication().executeOnPooledThread(accessor
);
94 accessorFuture
.get(DELAY_UNTIL_ABORT_CLIPBOARD_ACCESS
, TimeUnit
.MILLISECONDS
);
96 catch (InterruptedException e
) {
99 catch (TimeoutException e
) {
102 catch (ExecutionException e
) {
106 if (success
[0]) return contents
[0];
107 accessorFuture
.cancel(true);
108 showWorkaroundMessage();
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;
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;
167 void setSystemClipboardContent(final Transferable content
) {
168 final boolean[] success
= new boolean[]{false};
169 final Runnable accessor
= new Runnable() {
172 for (int i
= 0; i
< 3; i
++) {
174 Toolkit
.getDefaultToolkit().getSystemClipboard().setContents(content
, CopyPasteManagerEx
.this);
176 catch (IllegalStateException e
) {
180 catch (InterruptedException e1
) {
189 Thread
.interrupted(); // reset interrupted status
194 if (Patches
.SUN_BUG_ID_4818143
) {
195 Future
<?
> accessorFuture
= ApplicationManager
.getApplication().executeOnPooledThread(accessor
);
198 accessorFuture
.get(DELAY_UNTIL_ABORT_CLIPBOARD_ACCESS
, TimeUnit
.MILLISECONDS
);
200 catch (Exception e
) { /* no luck */ }
203 showWorkaroundMessage();
204 accessorFuture
.cancel(true);
212 private void addNewContentToStack(Transferable content
) {
214 String clipString
= getStringContent(content
);
215 if (clipString
!= null) {
216 Transferable same
= null;
217 for (Transferable old
: myDatas
) {
218 if (clipString
.equals(getStringContent(old
))) {
225 myDatas
.add(0, content
);
226 deleteAfterAllowedMaximum();
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
--) {
248 public Transferable
getContents() {
249 return getSystemClipboardContents();
252 public Transferable
[] getAllContents() {
253 deleteAfterAllowedMaximum();
255 Transferable content
= getSystemClipboardContents();
256 if (content
!= null) {
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;
280 if (isCurrentClipboardContent
) {
281 if (!myDatas
.isEmpty()) {
282 setSystemClipboardContent(myDatas
.get(0));
285 setSystemClipboardContent(new StringSelection(""));
288 fireContentChanged(old
);
291 public void moveContentTopStackTop(Transferable t
) {
292 setSystemClipboardContent(t
);