Add CopyableLabel to support copying text in one action
This was pulled out of Gerrit (fa6ee5de8ef0573c65be9aaa3edc566c24aadd5a)
and is based on Tom Preston-Werner's clippy.swf, a Flash movie which can
be used to copy text onto the system clipboard.
See also http://github.com/mojombo/clippy/tree/master
Signed-off-by: Shawn O. Pearce <sop@google.com>
diff --git a/src/main/java/com/google/gwtexpui/clippy/Clippy.gwt.xml b/src/main/java/com/google/gwtexpui/clippy/Clippy.gwt.xml
new file mode 100644
index 0000000..f211033
--- /dev/null
+++ b/src/main/java/com/google/gwtexpui/clippy/Clippy.gwt.xml
@@ -0,0 +1,20 @@
+<!--
+ Copyright 2009 Google Inc.
+
+ 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.
+-->
+<module>
+ <inherits name="com.google.gwtexpui.safehtml.SafeHtml"/>
+ <inherits name="com.google.gwtexpui.user.User"/>
+ <stylesheet src='gwtexpui_clippy1.cache.css' />
+</module>
diff --git a/src/main/java/com/google/gwtexpui/clippy/client/CopyableLabel.java b/src/main/java/com/google/gwtexpui/clippy/client/CopyableLabel.java
new file mode 100644
index 0000000..46f83e3
--- /dev/null
+++ b/src/main/java/com/google/gwtexpui/clippy/client/CopyableLabel.java
@@ -0,0 +1,197 @@
+// Copyright 2009 Google Inc.
+//
+// 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.GWT;
+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.DeferredCommand;
+import com.google.gwt.user.client.Element;
+import com.google.gwt.user.client.ui.ClickListener;
+import com.google.gwt.user.client.ui.Composite;
+import com.google.gwt.user.client.ui.FlowPanel;
+import com.google.gwt.user.client.ui.FocusListener;
+import com.google.gwt.user.client.ui.HasText;
+import com.google.gwt.user.client.ui.InlineLabel;
+import com.google.gwt.user.client.ui.KeyboardListenerAdapter;
+import com.google.gwt.user.client.ui.Label;
+import com.google.gwt.user.client.ui.TextBox;
+import com.google.gwt.user.client.ui.Widget;
+import com.google.gwtexpui.safehtml.client.SafeHtml;
+import com.google.gwtexpui.safehtml.client.SafeHtmlBuilder;
+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.
+ * <p>
+ * Style name: <code>gwtexpui-Clippy</code>
+ */
+public class CopyableLabel extends Composite implements HasText {
+ private static final int SWF_WIDTH = 110;
+ private static final int SWF_HEIGHT = 14;
+ private static String swfUrl;
+
+ private static String swfUrl() {
+ if (swfUrl == null) {
+ swfUrl = GWT.getModuleBaseURL() + "gwtexpui_clippy1.cache.swf";
+ }
+ return swfUrl;
+ }
+
+ private final FlowPanel content;
+ private String text;
+ private Label textLabel;
+ private TextBox textBox;
+ private Element swf;
+
+ /**
+ * 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();
+ content.setStyleName("gwtexpui-Clippy");
+ initWidget(content);
+
+ text = str;
+ if (showLabel) {
+ textLabel = new InlineLabel(getText());
+ textLabel.setStyleName("gwtexpui-Clippy-Label");
+ textLabel.addClickListener(new ClickListener() {
+ public void onClick(final Widget sender) {
+ showTextBox();
+ }
+ });
+ content.add(textLabel);
+ }
+ embedMovie();
+ }
+
+ private void embedMovie() {
+ if (UserAgent.hasFlash) {
+ final String flashVars = "text=" + URL.encodeComponent(getText());
+ final SafeHtmlBuilder h = new SafeHtmlBuilder();
+
+ h.openElement("span");
+ h.setStyleName("gwtexpui-Clippy-Control");
+
+ 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("type", "application/x-shockwave-flash");
+ h.setAttribute("src", swfUrl());
+ h.setAttribute("FlashVars", flashVars);
+ h.closeSelf();
+
+ h.closeElement("object");
+ h.closeElement("span");
+
+ if (swf != null) {
+ DOM.removeChild(getElement(), swf);
+ }
+ DOM.appendChild(getElement(), swf = SafeHtml.parse(h));
+ }
+ }
+
+ public String getText() {
+ return text;
+ }
+
+ public void setText(final String newText) {
+ text = newText;
+
+ 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(getText().length());
+ textBox.addKeyboardListener(new KeyboardListenerAdapter() {
+ @Override
+ public void onKeyPress(final Widget sender, final char kc, final int mod) {
+ if ((mod & MODIFIER_CTRL) == MODIFIER_CTRL
+ || (mod & MODIFIER_META) == MODIFIER_META) {
+ switch (kc) {
+ case 'c':
+ case 'x':
+ DeferredCommand.addCommand(new Command() {
+ public void execute() {
+ hideTextBox();
+ }
+ });
+ break;
+ }
+ }
+ }
+ });
+ textBox.addFocusListener(new FocusListener() {
+ public void onFocus(Widget arg0) {
+ }
+
+ public void onLostFocus(Widget arg0) {
+ hideTextBox();
+ }
+ });
+ content.insert(textBox, 1);
+ }
+
+ textLabel.setVisible(false);
+ textBox.setVisible(true);
+ textBox.selectAll();
+ textBox.setFocus(true);
+ }
+
+ private void hideTextBox() {
+ if (textBox != null) {
+ textBox.removeFromParent();
+ textBox = null;
+ }
+ textLabel.setVisible(true);
+ }
+}
diff --git a/src/main/java/com/google/gwtexpui/clippy/public/gwtexpui_clippy1.cache.css b/src/main/java/com/google/gwtexpui/clippy/public/gwtexpui_clippy1.cache.css
new file mode 100644
index 0000000..25ca9e8
--- /dev/null
+++ b/src/main/java/com/google/gwtexpui/clippy/public/gwtexpui_clippy1.cache.css
@@ -0,0 +1,8 @@
+.gwtexpui-Clippy {
+}
+.gwtexpui-Clippy-Label {
+ vertical-align: top;
+}
+.gwtexpui-Clippy-Control {
+ margin-left: 5px;
+}
diff --git a/src/main/java/com/google/gwtexpui/clippy/public/gwtexpui_clippy1.cache.swf b/src/main/java/com/google/gwtexpui/clippy/public/gwtexpui_clippy1.cache.swf
new file mode 100644
index 0000000..e46886c
--- /dev/null
+++ b/src/main/java/com/google/gwtexpui/clippy/public/gwtexpui_clippy1.cache.swf
Binary files differ
diff --git a/src/main/java/com/google/gwtexpui/user/User.gwt.xml b/src/main/java/com/google/gwtexpui/user/User.gwt.xml
new file mode 100644
index 0000000..9a94484
--- /dev/null
+++ b/src/main/java/com/google/gwtexpui/user/User.gwt.xml
@@ -0,0 +1,18 @@
+<!--
+ Copyright 2009 Google Inc.
+
+ 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.
+-->
+<module>
+ <inherits name="com.google.gwt.user.User"/>
+</module>
diff --git a/src/main/java/com/google/gwtexpui/user/client/UserAgent.java b/src/main/java/com/google/gwtexpui/user/client/UserAgent.java
new file mode 100644
index 0000000..e03b668
--- /dev/null
+++ b/src/main/java/com/google/gwtexpui/user/client/UserAgent.java
@@ -0,0 +1,50 @@
+// Copyright 2009 Google Inc.
+//
+// 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.user.client;
+
+/**
+ * User agent feature tests we don't create permutations for.
+ * <p>
+ * Some features aren't worth creating full permutations in GWT for, as each new
+ * boolean permutation (only two settings) doubles the compile time required. If
+ * the setting only affects a couple of lines of JavaScript code, the slightly
+ * larger cache files for user agents that lack the functionality requested is
+ * trivial compared to the time developers lose building their application.
+ */
+public class UserAgent {
+ /** Does the browser have ShockwaveFlash plugin enabled? */
+ public static final boolean hasFlash = hasFlash();
+
+ private static native boolean hasFlash()
+ /*-{
+ if (navigator.plugins && navigator.plugins.length) {
+ if (navigator.plugins['Shockwave Flash']) return true;
+ if (navigator.plugins['Shockwave Flash 2.0']) return true;
+
+ } else if (navigator.mimeTypes && navigator.mimeTypes.length) {
+ var mimeType = navigator.mimeTypes['application/x-shockwave-flash'];
+ if (mimeType && mimeType.enabledPlugin) return true;
+
+ } else {
+ try { new ActiveXObject('ShockwaveFlash.ShockwaveFlash.7'); return true; } catch (e) {}
+ try { new ActiveXObject('ShockwaveFlash.ShockwaveFlash.6'); return true; } catch (e) {}
+ try { new ActiveXObject('ShockwaveFlash.ShockwaveFlash'); return true; } catch (e) {}
+ }
+ return false;
+ }-*/;
+
+ private UserAgent() {
+ }
+}