sagar sagar - 6 months ago 33
Javascript Question

LibGDX html clipboard

I'm developing game in LibGDX, there are login screen and registration screen in game. HTML version of game has its sandbox environment for clipboard, means:

Anything copied from game, can not be pasted outside game &
anything copied from outside can not be pasted in textfield of game

I'm just trying to copy text, Is there any way I can merge sandbox clipboard with system clipboard?

What I want is:
when user does Ctr+V in textfield, it should grab text from system clipboard in textfield & when user press Ctr+C: it should put text in system clipboard

What I'm trying:

public class HtmlLauncher extends GwtApplication {
private static HtmlLauncher instance;
public void onModuleLoad() {
instance = this;
setLoadingListener(new LoadingListener() {
@Override
public void beforeSetup() {}

@Override
public void afterSetup() {
setupCopyListener();
}
});
}
native void setupCopyListener()
/*-{
var htmlLauncher_onCopy = $entry(@com.myapp.game.client.HtmlLauncher::addToClipboard());
$wnd.addEventListener("copy", htmlLauncher_onCopy, false);
}-*/
;

public static void addToClipboard() {
instance.copy();
}

private void copy() {
//getClipboard().setContents("");
consoleLog("copied");
}
}


Can anyone help me with:


  • how to grab parameter to event (to grab text copied)

  • This gets fired only when copy event occurs in DOM, how do I get system clipboard



edit (May 2, tried suggestion of JustACluelessNewbie):
inheriting Clipboard:

public class MyClipboard implements com.badlogic.gdx.utils.Clipboard{
private String cachedContent = "";

public MyClipboard() {
createTextArea();
}

@Override
public String getContents() {
String contents = getClipBoard();
return (contents == null) ? cachedContent : cachedContent = contents;
}

@Override
public void setContents(String content) {
cachedContent = content == null ? "" : content;
setClipBoard(content);
}

public static native void createTextArea() /*-{
var textArea = document.createElement('textarea');
textArea.style.position='fixed';
textArea.style.top=0;
textArea.style.left=0;
textArea.style.width='2em';
textArea.style.height='2em';
textArea.style.padding=0;
textArea.style.border='none';
textArea.style.outline='none';
textArea.style.boxShadow='none';
textArea.style.background='transparent';
$wnd._copy=textArea;
}-*/;

public static native String getClipBoard() /*-{
if(window.clipboardData){
return window.clipboardData.getData('Text');
}else {
document.body.appendChild($wnd._copy);
try{
$wnd._copy.value = "";
$wnd._copy.focus();
$wnd._copy.select();
console.log(document.queryCommandSupported("paste")); //prints true
var result = document.execCommand('paste');
console.log(result); //prints false
return $wnd._copy.value;
}catch(err){
return null;
}finally{
document.body.removeChild($wnd._copy);
}
}
}-*/;

public static native void setClipBoard(String content) /*-{
document.body.appendChild($wnd._copy);
try{
$wnd._copy.value = content;
$wnd._copy.select();
var result = document.execCommand('copy');
console.log("after exec copy "+result)
}catch(err){
console.log("error:"+err);
}finally{
document.body.removeChild($wnd._copy);
}
}-*/ ;
}



  • I'm running on latest chrome, it says it supports paste command but
    doesn't paste

  • It gets copied to system clipboard, I can see in clipboard but it
    doesn't paste on pressing Ctr+V or right click paste


Answer

Finally I got it working, I'm posting my answer if anyone is still looking for this

package myPackage;

public class HtmlLauncher extends GwtApplication {
    private static HtmlLauncher instance;

    @Override
    public void onModuleLoad() {
        super.onModuleLoad();
        instance = this;
        setLoadingListener(new LoadingListener() {
            @Override
            public void beforeSetup() {
            }

            @Override
            public void afterSetup() {
                setupCopyListener();
            }
        });
    }

    native void setupCopyListener() /*-{
        var self = this;
        var isSafari = navigator.appVersion.search('Safari') != -1 && navigator.appVersion.search('Chrome') == -1 && navigator.appVersion.search('CrMo') == -1 && navigator.appVersion.search('CriOS') == -1;
        var isIe = (navigator.userAgent.toLowerCase().indexOf("msie") != -1 || navigator.userAgent.toLowerCase().indexOf("trident") != -1);

        var ieClipboardDiv = $doc.getElementById('#ie-clipboard-contenteditable');
        var hiddenInput = $doc.getElementById("hidden-input");
        var getTextToCopy = $entry(function(){
            return self.@myPackage.HtmlLauncher::copy()();
        });
        var pasteText = $entry(function(text){
            self.@myPackage.HtmlLauncher::paste(Ljava/lang/String;)(text);
        });

        var focusHiddenArea = function() {
            // In order to ensure that the browser will fire clipboard events, we always need to have something selected
            hiddenInput.value = '';
            hiddenInput.focus();
            hiddenInput.select();
        };

        // Focuses an element to be ready for copy/paste (used exclusively for IE)
        var focusIeClipboardDiv = function() {
            ieClipboardDiv.focus();
            var range = document.createRange();
            range.selectNodeContents((ieClipboardDiv.get(0)));
            var selection = window.getSelection();
            selection.removeAllRanges();
            selection.addRange(range);
        };

        // For IE, we can get/set Text or URL just as we normally would,
        // but to get HTML, we need to let the browser perform the copy or paste
        // in a contenteditable div.
        var ieClipboardEvent = function(clipboardEvent) {
            var clipboardData = window.clipboardData;
            if (clipboardEvent == 'cut' || clipboardEvent == 'copy') {
                clipboardData.setData('Text', getTextToCopy());
                focusIeClipboardDiv();
                setTimeout(function() {
                    focusHiddenArea();
                    ieClipboardDiv.empty();
                }, 0);
            }
            if (clipboardEvent == 'paste') {
                var clipboardText = clipboardData.getData('Text');
                ieClipboardDiv.empty();
                setTimeout(function() {
                    pasteText(clipboardText);
                    ieClipboardDiv.empty();
                    focusHiddenArea();
                }, 0);
            }
        };

        // For every broswer except IE, we can easily get and set data on the clipboard
        var standardClipboardEvent = function(clipboardEvent, event) {
            var clipboardData = event.clipboardData;
            if (clipboardEvent == 'cut' || clipboardEvent == 'copy') {
                clipboardData.setData('text/plain', getTextToCopy());
            }
            if (clipboardEvent == 'paste') {
                pasteText(clipboardData.getData('text/plain'));
            }
        };

        ['cut', 'copy', 'paste'].forEach(function (event) {
            $doc.addEventListener(event,function (e) {
                console.log(event);
                if(isIe) {
                    ieClipboardEvent(event);
                } else {
                    standardClipboardEvent(event, e);
                    focusHiddenArea();
                    e.preventDefault();
                }
            })
        })
    }-*/;

    private void paste(String text) {
        consoleLog("in paste"+text);
        String oldText = getClipboard().getContents();
        if(!oldText.equals(text)){
            getClipboard().setContents(text);
            Actor focusedActor = ((BasicScreen)((Game)getApplicationListener()).getScreen()).getStage().getKeyboardFocus();
            if (focusedActor != null && focusedActor instanceof TextField) {
                if(!oldText.equals("")) {
                    String textFieldText = ((TextField)focusedActor).getText();
                    textFieldText = textFieldText.substring(0,textFieldText.lastIndexOf(oldText));
                    ((TextField)focusedActor).setText(textFieldText);
                }
                ((TextField)focusedActor).appendText(text);
            }
        }
    }

    private String copy() {
        return getClipboard().getContents();
    }
}

I have created one super class for all screens, minimum structure required for this problem:

public abstract class BasicScreen extends ScreenAdapter implements InputProcessor {
    protected MyStage stage;

    public BasicScreen () {
        stage = new MyStage();
    }

     public MyStage getStage () {
        return stage;
     }
}

you need to add textfield in same stage

in index.html, add this 2 line inside:

<div id="ie-clipboard-contenteditable" class="hidden" contenteditable="true"></div>
<input id="hidden-input" class="hidden" type="text" value=""/>

in css, add this:

.hidden {
    position: fixed;
    bottom: 0;
    left: 0;
    width: 10px;
    height: 10px;
    display: block;
    font-size: 1;
    z-index: -1;
    color: transparent;
    background: transparent;
    overflow: hidden;
    border: none;
    padding: 0;
    resize: none;
    outline: none;
    -webkit-user-select: text;
    user-select: text;
    /* Because for user-select:none, Safari won't allow input */
}

It works perfectly in chrome, did't test on other browsers.

I took reference from: https://www.lucidchart.com/techblog/2014/12/02/definitive-guide-copying-pasting-javascript/