// Copyright (C) 2009 The Android Open Source Project // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package com.google.gwtexpui.clippy.client; import com.google.gwt.core.client.Scheduler; import com.google.gwt.dom.client.Element; import com.google.gwt.dom.client.Style.Display; import com.google.gwt.event.dom.client.BlurEvent; import com.google.gwt.event.dom.client.BlurHandler; import com.google.gwt.event.dom.client.ClickEvent; import com.google.gwt.event.dom.client.ClickHandler; import com.google.gwt.event.dom.client.KeyPressEvent; import com.google.gwt.event.dom.client.KeyPressHandler; import com.google.gwt.event.dom.client.KeyUpEvent; import com.google.gwt.event.dom.client.KeyUpHandler; import com.google.gwt.event.dom.client.MouseOutEvent; import com.google.gwt.event.dom.client.MouseOutHandler; import com.google.gwt.http.client.URL; import com.google.gwt.user.client.Command; import com.google.gwt.user.client.DOM; import com.google.gwt.user.client.ui.Button; import com.google.gwt.user.client.ui.Composite; import com.google.gwt.user.client.ui.FlowPanel; import com.google.gwt.user.client.ui.HasText; import com.google.gwt.user.client.ui.InlineLabel; import com.google.gwt.user.client.ui.Label; import com.google.gwt.user.client.ui.TextBox; import com.google.gwtexpui.safehtml.client.SafeHtml; import com.google.gwtexpui.safehtml.client.SafeHtmlBuilder; import com.google.gwtexpui.user.client.Tooltip; import com.google.gwtexpui.user.client.UserAgent; /** * Label which permits the user to easily copy the complete content. * * <p>If the Flash plugin is available a "movie" is embedded that provides one-click copying of the * content onto the system clipboard. The label (if visible) can also be clicked, switching from a * label to an input box, allowing the user to copy the text with a keyboard shortcut. */ public class CopyableLabel extends Composite implements HasText { private static final int SWF_WIDTH = 110; private static final int SWF_HEIGHT = 14; private static boolean flashEnabled = true; static { ClippyResources.I.css().ensureInjected(); } public static boolean isFlashEnabled() { return flashEnabled; } public static void setFlashEnabled(final boolean on) { flashEnabled = on; } private static String swfUrl() { return ClippyResources.I.swf().getSafeUri().asString(); } private final FlowPanel content; private String text; private int visibleLen; private Label textLabel; private TextBox textBox; private Button copier; private Element swf; public CopyableLabel() { this(""); } /** * Create a new label * * @param str initial content */ public CopyableLabel(final String str) { this(str, true); } /** * Create a new label * * @param str initial content * @param showLabel if true, the content is shown, if false it is hidden from view and only the * copy icon is displayed. */ public CopyableLabel(final String str, final boolean showLabel) { content = new FlowPanel(); initWidget(content); text = str; visibleLen = text.length(); if (showLabel) { textLabel = new InlineLabel(getText()); textLabel.setStyleName(ClippyResources.I.css().label()); textLabel.addClickHandler( new ClickHandler() { @Override public void onClick(final ClickEvent event) { showTextBox(); } }); content.add(textLabel); } if (UserAgent.hasJavaScriptClipboard()) { copier = new Button( new SafeHtmlBuilder() .openElement("img") .setAttribute("src", ClippyResources.I.clipboard().getSafeUri().asString()) .setWidth(14) .setHeight(14) .closeSelf()); copier.setStyleName(ClippyResources.I.css().copier()); Tooltip.addStyle(copier); Tooltip.setLabel(copier, CopyableLabelText.I.tooltip()); copier.addClickHandler( new ClickHandler() { @Override public void onClick(ClickEvent event) { copy(); } }); copier.addMouseOutHandler( new MouseOutHandler() { @Override public void onMouseOut(MouseOutEvent event) { Tooltip.setLabel(copier, CopyableLabelText.I.tooltip()); } }); FlowPanel p = new FlowPanel(); p.getElement().getStyle().setDisplay(Display.INLINE_BLOCK); p.add(copier); content.add(p); } else { embedMovie(); } } /** * Change the text which is displayed in the clickable label. * * @param text the new preview text, should be shorter than the original text which would be * copied to the clipboard. */ public void setPreviewText(final String text) { if (textLabel != null) { textLabel.setText(text); } } private void embedMovie() { if (copier == null && flashEnabled && !text.isEmpty() && UserAgent.Flash.isInstalled()) { final String flashVars = "text=" + URL.encodeQueryString(getText()); final SafeHtmlBuilder h = new SafeHtmlBuilder(); h.openElement("div"); h.setStyleName(ClippyResources.I.css().swf()); h.openElement("object"); h.setWidth(SWF_WIDTH); h.setHeight(SWF_HEIGHT); h.setAttribute("classid", "clsid:d27cdb6e-ae6d-11cf-96b8-444553540000"); h.paramElement("movie", swfUrl()); h.paramElement("FlashVars", flashVars); h.openElement("embed"); h.setWidth(SWF_WIDTH); h.setHeight(SWF_HEIGHT); h.setAttribute("wmode", "transparent"); h.setAttribute("type", "application/x-shockwave-flash"); h.setAttribute("src", swfUrl()); h.setAttribute("FlashVars", flashVars); h.closeSelf(); h.closeElement("object"); h.closeElement("div"); if (swf != null) { getElement().removeChild(swf); } DOM.appendChild(getElement(), swf = SafeHtml.parse(h)); } } @Override public String getText() { return text; } @Override public void setText(final String newText) { text = newText; visibleLen = newText.length(); if (textLabel != null) { textLabel.setText(getText()); } if (textBox != null) { textBox.setText(getText()); textBox.selectAll(); } embedMovie(); } private void showTextBox() { if (textBox == null) { textBox = new TextBox(); textBox.setText(getText()); textBox.setVisibleLength(visibleLen); textBox.setReadOnly(true); textBox.addKeyPressHandler( new KeyPressHandler() { @Override public void onKeyPress(final KeyPressEvent event) { if (event.isControlKeyDown() || event.isMetaKeyDown()) { switch (event.getCharCode()) { case 'c': case 'x': textBox.addKeyUpHandler( new KeyUpHandler() { @Override public void onKeyUp(final KeyUpEvent event) { Scheduler.get() .scheduleDeferred( new Command() { @Override public void execute() { hideTextBox(); } }); } }); break; } } } }); textBox.addBlurHandler( new BlurHandler() { @Override public void onBlur(final BlurEvent event) { hideTextBox(); } }); content.insert(textBox, 1); } textLabel.setVisible(false); textBox.setVisible(true); Scheduler.get() .scheduleDeferred( new Command() { @Override public void execute() { textBox.selectAll(); textBox.setFocus(true); } }); } private void hideTextBox() { if (textBox != null) { textBox.removeFromParent(); textBox = null; } textLabel.setVisible(true); } private void copy() { TextBox t = new TextBox(); try { t.setText(getText()); content.add(t); t.setFocus(true); t.selectAll(); boolean ok = execCommand("copy"); Tooltip.setLabel(copier, ok ? CopyableLabelText.I.copied() : CopyableLabelText.I.failed()); if (!ok) { // Disable JavaScript clipboard and try flash movie in another instance. UserAgent.disableJavaScriptClipboard(); } } finally { t.removeFromParent(); } } private static boolean execCommand(String command) { try { return nativeExec(command); } catch (Exception e) { return false; } } private static native boolean nativeExec(String c) /*-{ return !! $doc.execCommand(c) }-*/; }