You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

VRichTextToolbar.java 17KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500
  1. /*
  2. * Copyright 2000-2018 Vaadin Ltd.
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License"); you may not
  5. * use this file except in compliance with the License. You may obtain a copy of
  6. * the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
  12. * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
  13. * License for the specific language governing permissions and limitations under
  14. * the License.
  15. */
  16. /*
  17. * Copyright 2007 Google Inc.
  18. *
  19. * Licensed under the Apache License, Version 2.0 (the "License"); you may not
  20. * use this file except in compliance with the License. You may obtain a copy of
  21. * the License at
  22. *
  23. * http://www.apache.org/licenses/LICENSE-2.0
  24. *
  25. * Unless required by applicable law or agreed to in writing, software
  26. * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
  27. * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
  28. * License for the specific language governing permissions and limitations under
  29. * the License.
  30. */
  31. package com.vaadin.client.ui.richtextarea;
  32. import com.google.gwt.core.client.GWT;
  33. import com.google.gwt.event.dom.client.ChangeEvent;
  34. import com.google.gwt.event.dom.client.ChangeHandler;
  35. import com.google.gwt.event.dom.client.ClickEvent;
  36. import com.google.gwt.event.dom.client.ClickHandler;
  37. import com.google.gwt.event.dom.client.KeyUpEvent;
  38. import com.google.gwt.event.dom.client.KeyUpHandler;
  39. import com.google.gwt.i18n.client.Constants;
  40. import com.google.gwt.resources.client.ClientBundle;
  41. import com.google.gwt.resources.client.ImageResource;
  42. import com.google.gwt.user.client.Window;
  43. import com.google.gwt.user.client.ui.Composite;
  44. import com.google.gwt.user.client.ui.FlowPanel;
  45. import com.google.gwt.user.client.ui.Image;
  46. import com.google.gwt.user.client.ui.ListBox;
  47. import com.google.gwt.user.client.ui.PushButton;
  48. import com.google.gwt.user.client.ui.RichTextArea;
  49. import com.google.gwt.user.client.ui.ToggleButton;
  50. /**
  51. * A modified version of sample toolbar for use with {@link RichTextArea}. It
  52. * provides a simple UI for all rich text formatting, dynamically displayed only
  53. * for the available functionality.
  54. */
  55. public class VRichTextToolbar extends Composite {
  56. /**
  57. * This {@link ClientBundle} is used for all the button icons. Using a
  58. * bundle allows all of these images to be packed into a single image, which
  59. * saves a lot of HTTP requests, drastically improving startup time.
  60. */
  61. public interface Images extends ClientBundle {
  62. ImageResource bold();
  63. ImageResource createLink();
  64. ImageResource hr();
  65. ImageResource indent();
  66. ImageResource insertImage();
  67. ImageResource italic();
  68. ImageResource justifyCenter();
  69. ImageResource justifyLeft();
  70. ImageResource justifyRight();
  71. ImageResource ol();
  72. ImageResource outdent();
  73. ImageResource removeFormat();
  74. ImageResource removeLink();
  75. ImageResource strikeThrough();
  76. ImageResource subscript();
  77. ImageResource superscript();
  78. ImageResource ul();
  79. ImageResource underline();
  80. }
  81. /**
  82. * This {@link Constants} interface is used to make the toolbar's strings
  83. * internationalizable.
  84. */
  85. public interface Strings extends Constants {
  86. String black();
  87. String blue();
  88. String bold();
  89. String color();
  90. String createLink();
  91. String font();
  92. String green();
  93. String hr();
  94. String indent();
  95. String insertImage();
  96. String italic();
  97. String justifyCenter();
  98. String justifyLeft();
  99. String justifyRight();
  100. String large();
  101. String medium();
  102. String normal();
  103. String ol();
  104. String outdent();
  105. String red();
  106. String removeFormat();
  107. String removeLink();
  108. String size();
  109. String small();
  110. String strikeThrough();
  111. String subscript();
  112. String superscript();
  113. String ul();
  114. String underline();
  115. String white();
  116. String xlarge();
  117. String xsmall();
  118. String xxlarge();
  119. String xxsmall();
  120. String yellow();
  121. }
  122. /**
  123. * We use an inner EventHandler class to avoid exposing event methods on the
  124. * RichTextToolbar itself.
  125. */
  126. private class EventHandler
  127. implements ClickHandler, ChangeHandler, KeyUpHandler {
  128. @Override
  129. @SuppressWarnings("deprecation")
  130. public void onChange(ChangeEvent event) {
  131. Object sender = event.getSource();
  132. if (sender == backColors) {
  133. basic.setBackColor(
  134. backColors.getValue(backColors.getSelectedIndex()));
  135. backColors.setSelectedIndex(0);
  136. } else if (sender == foreColors) {
  137. basic.setForeColor(
  138. foreColors.getValue(foreColors.getSelectedIndex()));
  139. foreColors.setSelectedIndex(0);
  140. } else if (sender == fonts) {
  141. basic.setFontName(fonts.getValue(fonts.getSelectedIndex()));
  142. fonts.setSelectedIndex(0);
  143. } else if (sender == fontSizes) {
  144. basic.setFontSize(
  145. FONT_SIZES_CONSTANTS[fontSizes.getSelectedIndex() - 1]);
  146. fontSizes.setSelectedIndex(0);
  147. }
  148. }
  149. @Override
  150. @SuppressWarnings("deprecation")
  151. public void onClick(ClickEvent event) {
  152. Object sender = event.getSource();
  153. if (sender == bold) {
  154. basic.toggleBold();
  155. } else if (sender == italic) {
  156. basic.toggleItalic();
  157. } else if (sender == underline) {
  158. basic.toggleUnderline();
  159. } else if (sender == subscript) {
  160. basic.toggleSubscript();
  161. } else if (sender == superscript) {
  162. basic.toggleSuperscript();
  163. } else if (sender == strikethrough) {
  164. extended.toggleStrikethrough();
  165. } else if (sender == indent) {
  166. extended.rightIndent();
  167. } else if (sender == outdent) {
  168. extended.leftIndent();
  169. } else if (sender == justifyLeft) {
  170. basic.setJustification(RichTextArea.Justification.LEFT);
  171. } else if (sender == justifyCenter) {
  172. basic.setJustification(RichTextArea.Justification.CENTER);
  173. } else if (sender == justifyRight) {
  174. basic.setJustification(RichTextArea.Justification.RIGHT);
  175. } else if (sender == insertImage) {
  176. final String url = Window.prompt("Enter an image URL:",
  177. "http://");
  178. if (url != null) {
  179. extended.insertImage(url);
  180. }
  181. } else if (sender == createLink) {
  182. final String url = Window.prompt("Enter a link URL:",
  183. "http://");
  184. if (url != null) {
  185. createLinkViaJSNI(extended, url);
  186. }
  187. } else if (sender == removeLink) {
  188. extended.removeLink();
  189. } else if (sender == hr) {
  190. extended.insertHorizontalRule();
  191. } else if (sender == ol) {
  192. extended.insertOrderedList();
  193. } else if (sender == ul) {
  194. extended.insertUnorderedList();
  195. } else if (sender == removeFormat) {
  196. extended.removeFormat();
  197. } else if (sender == richText) {
  198. // We use the RichTextArea's onKeyUp event to update the toolbar
  199. // status. This will catch any cases where the user moves the
  200. // cursur using the keyboard, or uses one of the browser's
  201. // built-in keyboard shortcuts.
  202. updateStatus();
  203. }
  204. }
  205. @Override
  206. public void onKeyUp(KeyUpEvent event) {
  207. if (event.getSource() == richText) {
  208. // We use the RichTextArea's onKeyUp event to update the toolbar
  209. // status. This will catch any cases where the user moves the
  210. // cursor using the keyboard, or uses one of the browser's
  211. // built-in keyboard shortcuts.
  212. updateStatus();
  213. }
  214. }
  215. private native void createLinkViaJSNI(
  216. RichTextArea.ExtendedFormatter formatter, String url)
  217. /*-{
  218. var elem = formatter.@com.google.gwt.user.client.ui.impl.RichTextAreaImpl::elem;
  219. var wnd = elem.contentWindow;
  220. var selectedText = "";
  221. if (wnd.getSelection) {
  222. selectedText = wnd.getSelection().toString();
  223. }
  224. wnd.focus();
  225. if (selectedText) {
  226. // Add url as the href property of the highlighted text
  227. wnd.document.execCommand("createLink", false, url);
  228. } else {
  229. // Insert url both as a new text and its href-property value
  230. var range = wnd.document.getSelection().getRangeAt(0)
  231. var node = wnd.document.createElement("a");
  232. node.innerHTML = url;
  233. node.setAttribute("href", url);
  234. range.insertNode(node);
  235. }
  236. }-*/;
  237. }
  238. private static final RichTextArea.FontSize[] FONT_SIZES_CONSTANTS = {
  239. RichTextArea.FontSize.XX_SMALL, RichTextArea.FontSize.X_SMALL,
  240. RichTextArea.FontSize.SMALL, RichTextArea.FontSize.MEDIUM,
  241. RichTextArea.FontSize.LARGE, RichTextArea.FontSize.X_LARGE,
  242. RichTextArea.FontSize.XX_LARGE };
  243. private final Images images = (Images) GWT.create(Images.class);
  244. private final Strings strings = (Strings) GWT.create(Strings.class);
  245. private final EventHandler handler = new EventHandler();
  246. private final RichTextArea richText;
  247. @SuppressWarnings("deprecation")
  248. private final RichTextArea.BasicFormatter basic;
  249. @SuppressWarnings("deprecation")
  250. private final RichTextArea.ExtendedFormatter extended;
  251. private final FlowPanel outer = new FlowPanel();
  252. private final FlowPanel topPanel = new FlowPanel();
  253. private final FlowPanel bottomPanel = new FlowPanel();
  254. private ToggleButton bold;
  255. private ToggleButton italic;
  256. private ToggleButton underline;
  257. private ToggleButton subscript;
  258. private ToggleButton superscript;
  259. private ToggleButton strikethrough;
  260. private PushButton indent;
  261. private PushButton outdent;
  262. private PushButton justifyLeft;
  263. private PushButton justifyCenter;
  264. private PushButton justifyRight;
  265. private PushButton hr;
  266. private PushButton ol;
  267. private PushButton ul;
  268. private PushButton insertImage;
  269. private PushButton createLink;
  270. private PushButton removeLink;
  271. private PushButton removeFormat;
  272. private ListBox backColors;
  273. private ListBox foreColors;
  274. private ListBox fonts;
  275. private ListBox fontSizes;
  276. /**
  277. * Creates a new toolbar that drives the given rich text area.
  278. *
  279. * @param richText
  280. * the rich text area to be controlled
  281. */
  282. @SuppressWarnings("deprecation")
  283. public VRichTextToolbar(RichTextArea richText) {
  284. this.richText = richText;
  285. basic = richText.getBasicFormatter();
  286. extended = richText.getExtendedFormatter();
  287. outer.add(topPanel);
  288. outer.add(bottomPanel);
  289. topPanel.setStyleName("gwt-RichTextToolbar-top");
  290. bottomPanel.setStyleName("gwt-RichTextToolbar-bottom");
  291. initWidget(outer);
  292. setStyleName("gwt-RichTextToolbar");
  293. if (basic != null) {
  294. topPanel.add(
  295. bold = createToggleButton(images.bold(), strings.bold()));
  296. topPanel.add(italic = createToggleButton(images.italic(),
  297. strings.italic()));
  298. topPanel.add(underline = createToggleButton(images.underline(),
  299. strings.underline()));
  300. topPanel.add(subscript = createToggleButton(images.subscript(),
  301. strings.subscript()));
  302. topPanel.add(superscript = createToggleButton(images.superscript(),
  303. strings.superscript()));
  304. topPanel.add(justifyLeft = createPushButton(images.justifyLeft(),
  305. strings.justifyLeft()));
  306. topPanel.add(justifyCenter = createPushButton(
  307. images.justifyCenter(), strings.justifyCenter()));
  308. topPanel.add(justifyRight = createPushButton(images.justifyRight(),
  309. strings.justifyRight()));
  310. }
  311. if (extended != null) {
  312. topPanel.add(strikethrough = createToggleButton(
  313. images.strikeThrough(), strings.strikeThrough()));
  314. topPanel.add(indent = createPushButton(images.indent(),
  315. strings.indent()));
  316. topPanel.add(outdent = createPushButton(images.outdent(),
  317. strings.outdent()));
  318. topPanel.add(hr = createPushButton(images.hr(), strings.hr()));
  319. topPanel.add(ol = createPushButton(images.ol(), strings.ol()));
  320. topPanel.add(ul = createPushButton(images.ul(), strings.ul()));
  321. topPanel.add(insertImage = createPushButton(images.insertImage(),
  322. strings.insertImage()));
  323. topPanel.add(createLink = createPushButton(images.createLink(),
  324. strings.createLink()));
  325. topPanel.add(removeLink = createPushButton(images.removeLink(),
  326. strings.removeLink()));
  327. topPanel.add(removeFormat = createPushButton(images.removeFormat(),
  328. strings.removeFormat()));
  329. }
  330. if (basic != null) {
  331. bottomPanel.add(backColors = createColorList("Background"));
  332. bottomPanel.add(foreColors = createColorList("Foreground"));
  333. bottomPanel.add(fonts = createFontList());
  334. bottomPanel.add(fontSizes = createFontSizes());
  335. // We only use these handlers for updating status, so don't hook
  336. // them up unless at least basic editing is supported.
  337. richText.addKeyUpHandler(handler);
  338. richText.addClickHandler(handler);
  339. }
  340. }
  341. private ListBox createColorList(String caption) {
  342. final ListBox lb = new ListBox();
  343. lb.addChangeHandler(handler);
  344. lb.setVisibleItemCount(1);
  345. lb.addItem(caption);
  346. lb.addItem(strings.white(), "white");
  347. lb.addItem(strings.black(), "black");
  348. lb.addItem(strings.red(), "red");
  349. lb.addItem(strings.green(), "green");
  350. lb.addItem(strings.yellow(), "yellow");
  351. lb.addItem(strings.blue(), "blue");
  352. lb.setTabIndex(-1);
  353. return lb;
  354. }
  355. private ListBox createFontList() {
  356. final ListBox lb = new ListBox();
  357. lb.addChangeHandler(handler);
  358. lb.setVisibleItemCount(1);
  359. lb.addItem(strings.font(), "");
  360. lb.addItem(strings.normal(), "inherit");
  361. lb.addItem("Times New Roman", "Times New Roman");
  362. lb.addItem("Arial", "Arial");
  363. lb.addItem("Courier New", "Courier New");
  364. lb.addItem("Georgia", "Georgia");
  365. lb.addItem("Trebuchet", "Trebuchet");
  366. lb.addItem("Verdana", "Verdana");
  367. lb.setTabIndex(-1);
  368. return lb;
  369. }
  370. private ListBox createFontSizes() {
  371. final ListBox lb = new ListBox();
  372. lb.addChangeHandler(handler);
  373. lb.setVisibleItemCount(1);
  374. lb.addItem(strings.size());
  375. lb.addItem(strings.xxsmall());
  376. lb.addItem(strings.xsmall());
  377. lb.addItem(strings.small());
  378. lb.addItem(strings.medium());
  379. lb.addItem(strings.large());
  380. lb.addItem(strings.xlarge());
  381. lb.addItem(strings.xxlarge());
  382. lb.setTabIndex(-1);
  383. return lb;
  384. }
  385. private PushButton createPushButton(ImageResource img, String tip) {
  386. final PushButton pb = new PushButton(new Image(img));
  387. pb.addClickHandler(handler);
  388. pb.setTitle(tip);
  389. pb.setTabIndex(-1);
  390. return pb;
  391. }
  392. private ToggleButton createToggleButton(ImageResource img, String tip) {
  393. final ToggleButton tb = new ToggleButton(new Image(img));
  394. tb.addClickHandler(handler);
  395. tb.setTitle(tip);
  396. tb.setTabIndex(-1);
  397. return tb;
  398. }
  399. /**
  400. * Updates the status of all the stateful buttons.
  401. */
  402. @SuppressWarnings("deprecation")
  403. private void updateStatus() {
  404. if (basic != null) {
  405. bold.setDown(basic.isBold());
  406. italic.setDown(basic.isItalic());
  407. underline.setDown(basic.isUnderlined());
  408. subscript.setDown(basic.isSubscript());
  409. superscript.setDown(basic.isSuperscript());
  410. }
  411. if (extended != null) {
  412. strikethrough.setDown(extended.isStrikethrough());
  413. }
  414. }
  415. }