comment.js 9.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284
  1. /**
  2. * @license
  3. * Visual Blocks Editor
  4. *
  5. * Copyright 2011 Google Inc.
  6. * https://developers.google.com/blockly/
  7. *
  8. * Licensed under the Apache License, Version 2.0 (the "License");
  9. * you may not use this file except in compliance with the License.
  10. * You may obtain a copy of the License at
  11. *
  12. * http://www.apache.org/licenses/LICENSE-2.0
  13. *
  14. * Unless required by applicable law or agreed to in writing, software
  15. * distributed under the License is distributed on an "AS IS" BASIS,
  16. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  17. * See the License for the specific language governing permissions and
  18. * limitations under the License.
  19. */
  20. /**
  21. * @fileoverview Object representing a code comment.
  22. * @author fraser@google.com (Neil Fraser)
  23. */
  24. 'use strict';
  25. goog.provide('Blockly.Comment');
  26. goog.require('Blockly.Bubble');
  27. goog.require('Blockly.Icon');
  28. goog.require('goog.userAgent');
  29. /**
  30. * Class for a comment.
  31. * @param {!Blockly.Block} block The block associated with this comment.
  32. * @extends {Blockly.Icon}
  33. * @constructor
  34. */
  35. Blockly.Comment = function(block) {
  36. Blockly.Comment.superClass_.constructor.call(this, block);
  37. this.createIcon();
  38. };
  39. goog.inherits(Blockly.Comment, Blockly.Icon);
  40. /**
  41. * Icon in base64 format.
  42. * @private
  43. */
  44. Blockly.Comment.prototype.png_ = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABEAAAARCAYAAAA7bUf6AAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAANyAAADcgBffIlqAAAAAd0SU1FB98DGgAnBf0Xj5sAAAIBSURBVDjLjZO9SxxRFMXPrFkWl2UFYSOIRtF210YtAiH/gGATRNZFgo19IBaB9Ipgk3SiEoKQgI19JIVgGaOIgpWJEAV1kZk3b1ad0V+KRYIzk5ALh1ecc88978tRSgHPg0Bjvq/BbFalMNR5oaBv+bzWHMfZjOudWPOg6+pDva6elRXlt7fVcnYmPX4sDQ3pdmpKQXu7frS16aXjON8T06OIMWOwtRp3jgNSEpkMTE5y5/v4UcSLePxnroutVNKb4xgYANfFAk/vDbLG8Gtk5P8M7jE6CsZwDDwSMLm5iYmLlpbg4ABOTmBjA4aHk0ZbWxigposLvlarScH5OSwvw9oaABwdJTW1GtTrfJHnUe/uTgqKxeZaKEAUgTEQP/CeHvA8LhRFhLlc+r6zWVhfbyaZn0/yuRxEEaGCAK9USjdZWGgarK5CS0uS7+gAa3EzjYaOy2WlludJi4vSzIx0e5vky2Xp6ko/M4WCPleruk4zsVa6vJSur9OHTEzoqljUJwEdQYDf25uMe3jY3E5fX5Lr7wdr8YGSJCkIeL23h9/a+lA4Pg7T039u6h75POzv4wcBrx5Ec11Wd3bwOzv//VK7umB3F991+Zj2/R1reWstdnaWm3L5YXOlAnNz3FiLbTR4Azj6WwFPjOG953EahoT1On4YEnoep8bwDuiO9/wG1sM4kG8A4fUAAAAASUVORK5CYII=';
  45. /**
  46. * Comment text (if bubble is not visible).
  47. * @private
  48. */
  49. Blockly.Comment.prototype.text_ = '';
  50. /**
  51. * Width of bubble.
  52. * @private
  53. */
  54. Blockly.Comment.prototype.width_ = 160;
  55. /**
  56. * Height of bubble.
  57. * @private
  58. */
  59. Blockly.Comment.prototype.height_ = 80;
  60. /**
  61. * Draw the comment icon.
  62. * @param {!Element} group The icon group.
  63. * @private
  64. */
  65. Blockly.Comment.prototype.drawIcon_ = function(group) {
  66. // Circle.
  67. Blockly.createSvgElement('circle',
  68. {'class': 'blocklyIconShape', 'r': '8', 'cx': '8', 'cy': '8'},
  69. group);
  70. // Can't use a real '?' text character since different browsers and operating
  71. // systems render it differently.
  72. // Body of question mark.
  73. Blockly.createSvgElement('path',
  74. {'class': 'blocklyIconSymbol',
  75. 'd': 'm6.8,10h2c0.003,-0.617 0.271,-0.962 0.633,-1.266 2.875,-2.405 0.607,-5.534 -3.765,-3.874v1.7c3.12,-1.657 3.698,0.118 2.336,1.25 -1.201,0.998 -1.201,1.528 -1.204,2.19z'},
  76. group);
  77. // Dot of question point.
  78. Blockly.createSvgElement('rect',
  79. {'class': 'blocklyIconSymbol',
  80. 'x': '6.8', 'y': '10.78', 'height': '2', 'width': '2'},
  81. group);
  82. };
  83. /**
  84. * Create the editor for the comment's bubble.
  85. * @return {!Element} The top-level node of the editor.
  86. * @private
  87. */
  88. Blockly.Comment.prototype.createEditor_ = function() {
  89. /* Create the editor. Here's the markup that will be generated:
  90. <foreignObject x="8" y="8" width="164" height="164">
  91. <body xmlns="http://www.w3.org/1999/xhtml" class="blocklyMinimalBody">
  92. <textarea xmlns="http://www.w3.org/1999/xhtml"
  93. class="blocklyCommentTextarea"
  94. style="height: 164px; width: 164px;"></textarea>
  95. </body>
  96. </foreignObject>
  97. */
  98. this.foreignObject_ = Blockly.createSvgElement('foreignObject',
  99. {'x': Blockly.Bubble.BORDER_WIDTH, 'y': Blockly.Bubble.BORDER_WIDTH},
  100. null);
  101. var body = document.createElementNS(Blockly.HTML_NS, 'body');
  102. body.setAttribute('xmlns', Blockly.HTML_NS);
  103. body.className = 'blocklyMinimalBody';
  104. var textarea = document.createElementNS(Blockly.HTML_NS, 'textarea');
  105. textarea.className = 'blocklyCommentTextarea';
  106. textarea.setAttribute('dir', this.block_.RTL ? 'RTL' : 'LTR');
  107. body.appendChild(textarea);
  108. this.textarea_ = textarea;
  109. this.foreignObject_.appendChild(body);
  110. Blockly.bindEvent_(textarea, 'mouseup', this, this.textareaFocus_);
  111. // Don't zoom with mousewheel.
  112. Blockly.bindEvent_(textarea, 'wheel', this, function(e) {
  113. e.stopPropagation();
  114. });
  115. Blockly.bindEvent_(textarea, 'change', this, function(e) {
  116. if (this.text_ != textarea.value) {
  117. Blockly.Events.fire(new Blockly.Events.Change(
  118. this.block_, 'comment', null, this.text_, textarea.value));
  119. this.text_ = textarea.value;
  120. }
  121. });
  122. setTimeout(function() {
  123. textarea.focus();
  124. }, 0);
  125. return this.foreignObject_;
  126. };
  127. /**
  128. * Add or remove editability of the comment.
  129. * @override
  130. */
  131. Blockly.Comment.prototype.updateEditable = function() {
  132. if (this.isVisible()) {
  133. // Toggling visibility will force a rerendering.
  134. this.setVisible(false);
  135. this.setVisible(true);
  136. }
  137. // Allow the icon to update.
  138. Blockly.Icon.prototype.updateEditable.call(this);
  139. };
  140. /**
  141. * Callback function triggered when the bubble has resized.
  142. * Resize the text area accordingly.
  143. * @private
  144. */
  145. Blockly.Comment.prototype.resizeBubble_ = function() {
  146. if (this.isVisible()) {
  147. var size = this.bubble_.getBubbleSize();
  148. var doubleBorderWidth = 2 * Blockly.Bubble.BORDER_WIDTH;
  149. this.foreignObject_.setAttribute('width', size.width - doubleBorderWidth);
  150. this.foreignObject_.setAttribute('height', size.height - doubleBorderWidth);
  151. this.textarea_.style.width = (size.width - doubleBorderWidth - 4) + 'px';
  152. this.textarea_.style.height = (size.height - doubleBorderWidth - 4) + 'px';
  153. }
  154. };
  155. /**
  156. * Show or hide the comment bubble.
  157. * @param {boolean} visible True if the bubble should be visible.
  158. */
  159. Blockly.Comment.prototype.setVisible = function(visible) {
  160. if (visible == this.isVisible()) {
  161. // No change.
  162. return;
  163. }
  164. Blockly.Events.fire(
  165. new Blockly.Events.Ui(this.block_, 'commentOpen', !visible, visible));
  166. if ((!this.block_.isEditable() && !this.textarea_) || goog.userAgent.IE) {
  167. // Steal the code from warnings to make an uneditable text bubble.
  168. // MSIE does not support foreignobject; textareas are impossible.
  169. // http://msdn.microsoft.com/en-us/library/hh834675%28v=vs.85%29.aspx
  170. // Always treat comments in IE as uneditable.
  171. Blockly.Warning.prototype.setVisible.call(this, visible);
  172. return;
  173. }
  174. // Save the bubble stats before the visibility switch.
  175. var text = this.getText();
  176. var size = this.getBubbleSize();
  177. if (visible) {
  178. // Create the bubble.
  179. this.bubble_ = new Blockly.Bubble(
  180. /** @type {!Blockly.WorkspaceSvg} */ (this.block_.workspace),
  181. this.createEditor_(), this.block_.svgPath_,
  182. this.iconXY_, this.width_, this.height_);
  183. this.bubble_.registerResizeEvent(this.resizeBubble_.bind(this));
  184. this.updateColour();
  185. } else {
  186. // Dispose of the bubble.
  187. this.bubble_.dispose();
  188. this.bubble_ = null;
  189. this.textarea_ = null;
  190. this.foreignObject_ = null;
  191. }
  192. // Restore the bubble stats after the visibility switch.
  193. this.setText(text);
  194. this.setBubbleSize(size.width, size.height);
  195. };
  196. /**
  197. * Bring the comment to the top of the stack when clicked on.
  198. * @param {!Event} e Mouse up event.
  199. * @private
  200. */
  201. Blockly.Comment.prototype.textareaFocus_ = function(e) {
  202. // Ideally this would be hooked to the focus event for the comment.
  203. // However doing so in Firefox swallows the cursor for unknown reasons.
  204. // So this is hooked to mouseup instead. No big deal.
  205. this.bubble_.promote_();
  206. // Since the act of moving this node within the DOM causes a loss of focus,
  207. // we need to reapply the focus.
  208. this.textarea_.focus();
  209. };
  210. /**
  211. * Get the dimensions of this comment's bubble.
  212. * @return {!Object} Object with width and height properties.
  213. */
  214. Blockly.Comment.prototype.getBubbleSize = function() {
  215. if (this.isVisible()) {
  216. return this.bubble_.getBubbleSize();
  217. } else {
  218. return {width: this.width_, height: this.height_};
  219. }
  220. };
  221. /**
  222. * Size this comment's bubble.
  223. * @param {number} width Width of the bubble.
  224. * @param {number} height Height of the bubble.
  225. */
  226. Blockly.Comment.prototype.setBubbleSize = function(width, height) {
  227. if (this.textarea_) {
  228. this.bubble_.setBubbleSize(width, height);
  229. } else {
  230. this.width_ = width;
  231. this.height_ = height;
  232. }
  233. };
  234. /**
  235. * Returns this comment's text.
  236. * @return {string} Comment text.
  237. */
  238. Blockly.Comment.prototype.getText = function() {
  239. return this.textarea_ ? this.textarea_.value : this.text_;
  240. };
  241. /**
  242. * Set this comment's text.
  243. * @param {string} text Comment text.
  244. */
  245. Blockly.Comment.prototype.setText = function(text) {
  246. if (this.text_ != text) {
  247. Blockly.Events.fire(new Blockly.Events.Change(
  248. this.block_, 'comment', null, this.text_, text));
  249. this.text_ = text;
  250. }
  251. if (this.textarea_) {
  252. this.textarea_.value = text;
  253. }
  254. };
  255. /**
  256. * Dispose of this comment.
  257. */
  258. Blockly.Comment.prototype.dispose = function() {
  259. if (Blockly.Events.isEnabled()) {
  260. this.setText(''); // Fire event to delete comment.
  261. }
  262. this.block_.comment = null;
  263. Blockly.Icon.prototype.dispose.call(this);
  264. };