comment.js 8.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278
  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. * Comment text (if bubble is not visible).
  42. * @private
  43. */
  44. Blockly.Comment.prototype.text_ = '';
  45. /**
  46. * Width of bubble.
  47. * @private
  48. */
  49. Blockly.Comment.prototype.width_ = 160;
  50. /**
  51. * Height of bubble.
  52. * @private
  53. */
  54. Blockly.Comment.prototype.height_ = 80;
  55. /**
  56. * Draw the comment icon.
  57. * @param {!Element} group The icon group.
  58. * @private
  59. */
  60. Blockly.Comment.prototype.drawIcon_ = function(group) {
  61. // Circle.
  62. Blockly.createSvgElement('circle',
  63. {'class': 'blocklyIconShape', 'r': '8', 'cx': '8', 'cy': '8'},
  64. group);
  65. // Can't use a real '?' text character since different browsers and operating
  66. // systems render it differently.
  67. // Body of question mark.
  68. Blockly.createSvgElement('path',
  69. {'class': 'blocklyIconSymbol',
  70. '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'},
  71. group);
  72. // Dot of question point.
  73. Blockly.createSvgElement('rect',
  74. {'class': 'blocklyIconSymbol',
  75. 'x': '6.8', 'y': '10.78', 'height': '2', 'width': '2'},
  76. group);
  77. };
  78. /**
  79. * Create the editor for the comment's bubble.
  80. * @return {!Element} The top-level node of the editor.
  81. * @private
  82. */
  83. Blockly.Comment.prototype.createEditor_ = function() {
  84. /* Create the editor. Here's the markup that will be generated:
  85. <foreignObject x="8" y="8" width="164" height="164">
  86. <body xmlns="http://www.w3.org/1999/xhtml" class="blocklyMinimalBody">
  87. <textarea xmlns="http://www.w3.org/1999/xhtml"
  88. class="blocklyCommentTextarea"
  89. style="height: 164px; width: 164px;"></textarea>
  90. </body>
  91. </foreignObject>
  92. */
  93. this.foreignObject_ = Blockly.createSvgElement('foreignObject',
  94. {'x': Blockly.Bubble.BORDER_WIDTH, 'y': Blockly.Bubble.BORDER_WIDTH},
  95. null);
  96. var body = document.createElementNS(Blockly.HTML_NS, 'body');
  97. body.setAttribute('xmlns', Blockly.HTML_NS);
  98. body.className = 'blocklyMinimalBody';
  99. var textarea = document.createElementNS(Blockly.HTML_NS, 'textarea');
  100. textarea.className = 'blocklyCommentTextarea';
  101. textarea.setAttribute('dir', this.block_.RTL ? 'RTL' : 'LTR');
  102. body.appendChild(textarea);
  103. this.textarea_ = textarea;
  104. this.foreignObject_.appendChild(body);
  105. Blockly.bindEventWithChecks_(textarea, 'mouseup', this, this.textareaFocus_);
  106. // Don't zoom with mousewheel.
  107. Blockly.bindEventWithChecks_(textarea, 'wheel', this, function(e) {
  108. e.stopPropagation();
  109. });
  110. Blockly.bindEventWithChecks_(textarea, 'change', this, function(e) {
  111. if (this.text_ != textarea.value) {
  112. Blockly.Events.fire(new Blockly.Events.Change(
  113. this.block_, 'comment', null, this.text_, textarea.value));
  114. this.text_ = textarea.value;
  115. }
  116. });
  117. setTimeout(function() {
  118. textarea.focus();
  119. }, 0);
  120. return this.foreignObject_;
  121. };
  122. /**
  123. * Add or remove editability of the comment.
  124. * @override
  125. */
  126. Blockly.Comment.prototype.updateEditable = function() {
  127. if (this.isVisible()) {
  128. // Toggling visibility will force a rerendering.
  129. this.setVisible(false);
  130. this.setVisible(true);
  131. }
  132. // Allow the icon to update.
  133. Blockly.Icon.prototype.updateEditable.call(this);
  134. };
  135. /**
  136. * Callback function triggered when the bubble has resized.
  137. * Resize the text area accordingly.
  138. * @private
  139. */
  140. Blockly.Comment.prototype.resizeBubble_ = function() {
  141. if (this.isVisible()) {
  142. var size = this.bubble_.getBubbleSize();
  143. var doubleBorderWidth = 2 * Blockly.Bubble.BORDER_WIDTH;
  144. this.foreignObject_.setAttribute('width', size.width - doubleBorderWidth);
  145. this.foreignObject_.setAttribute('height', size.height - doubleBorderWidth);
  146. this.textarea_.style.width = (size.width - doubleBorderWidth - 4) + 'px';
  147. this.textarea_.style.height = (size.height - doubleBorderWidth - 4) + 'px';
  148. }
  149. };
  150. /**
  151. * Show or hide the comment bubble.
  152. * @param {boolean} visible True if the bubble should be visible.
  153. */
  154. Blockly.Comment.prototype.setVisible = function(visible) {
  155. if (visible == this.isVisible()) {
  156. // No change.
  157. return;
  158. }
  159. Blockly.Events.fire(
  160. new Blockly.Events.Ui(this.block_, 'commentOpen', !visible, visible));
  161. if ((!this.block_.isEditable() && !this.textarea_) || goog.userAgent.IE) {
  162. // Steal the code from warnings to make an uneditable text bubble.
  163. // MSIE does not support foreignobject; textareas are impossible.
  164. // http://msdn.microsoft.com/en-us/library/hh834675%28v=vs.85%29.aspx
  165. // Always treat comments in IE as uneditable.
  166. Blockly.Warning.prototype.setVisible.call(this, visible);
  167. return;
  168. }
  169. // Save the bubble stats before the visibility switch.
  170. var text = this.getText();
  171. var size = this.getBubbleSize();
  172. if (visible) {
  173. // Create the bubble.
  174. this.bubble_ = new Blockly.Bubble(
  175. /** @type {!Blockly.WorkspaceSvg} */ (this.block_.workspace),
  176. this.createEditor_(), this.block_.svgPath_,
  177. this.iconXY_, this.width_, this.height_);
  178. this.bubble_.registerResizeEvent(this.resizeBubble_.bind(this));
  179. this.updateColour();
  180. } else {
  181. // Dispose of the bubble.
  182. this.bubble_.dispose();
  183. this.bubble_ = null;
  184. this.textarea_ = null;
  185. this.foreignObject_ = null;
  186. }
  187. // Restore the bubble stats after the visibility switch.
  188. this.setText(text);
  189. this.setBubbleSize(size.width, size.height);
  190. };
  191. /**
  192. * Bring the comment to the top of the stack when clicked on.
  193. * @param {!Event} e Mouse up event.
  194. * @private
  195. */
  196. Blockly.Comment.prototype.textareaFocus_ = function(e) {
  197. // Ideally this would be hooked to the focus event for the comment.
  198. // However doing so in Firefox swallows the cursor for unknown reasons.
  199. // So this is hooked to mouseup instead. No big deal.
  200. this.bubble_.promote_();
  201. // Since the act of moving this node within the DOM causes a loss of focus,
  202. // we need to reapply the focus.
  203. this.textarea_.focus();
  204. };
  205. /**
  206. * Get the dimensions of this comment's bubble.
  207. * @return {!Object} Object with width and height properties.
  208. */
  209. Blockly.Comment.prototype.getBubbleSize = function() {
  210. if (this.isVisible()) {
  211. return this.bubble_.getBubbleSize();
  212. } else {
  213. return {width: this.width_, height: this.height_};
  214. }
  215. };
  216. /**
  217. * Size this comment's bubble.
  218. * @param {number} width Width of the bubble.
  219. * @param {number} height Height of the bubble.
  220. */
  221. Blockly.Comment.prototype.setBubbleSize = function(width, height) {
  222. if (this.textarea_) {
  223. this.bubble_.setBubbleSize(width, height);
  224. } else {
  225. this.width_ = width;
  226. this.height_ = height;
  227. }
  228. };
  229. /**
  230. * Returns this comment's text.
  231. * @return {string} Comment text.
  232. */
  233. Blockly.Comment.prototype.getText = function() {
  234. return this.textarea_ ? this.textarea_.value : this.text_;
  235. };
  236. /**
  237. * Set this comment's text.
  238. * @param {string} text Comment text.
  239. */
  240. Blockly.Comment.prototype.setText = function(text) {
  241. if (this.text_ != text) {
  242. Blockly.Events.fire(new Blockly.Events.Change(
  243. this.block_, 'comment', null, this.text_, text));
  244. this.text_ = text;
  245. }
  246. if (this.textarea_) {
  247. this.textarea_.value = text;
  248. }
  249. };
  250. /**
  251. * Dispose of this comment.
  252. */
  253. Blockly.Comment.prototype.dispose = function() {
  254. if (Blockly.Events.isEnabled()) {
  255. this.setText(''); // Fire event to delete comment.
  256. }
  257. this.block_.comment = null;
  258. Blockly.Icon.prototype.dispose.call(this);
  259. };