source: gs3-extensions/seaweed-debug/trunk/src/Typing.js@ 25160

Last change on this file since 25160 was 25160, checked in by sjm84, 12 years ago

Initial cut at a version of seaweed for debugging purposes. Check it out live into the web/ext folder

File size: 16.2 KB
Line 
1/*
2 * file: Typing.js
3 *
4 * @BEGINLICENSE
5 * Copyright 2010 Brook Novak (email : [email protected])
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 * You should have received a copy of the GNU General Public License
15 * along with this program; if not, write to the Free Software
16 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
17 * @ENDLICENSE
18 */
19/*
20 * TODO: RESTRUCTURE TO SUPPORT ADDING AND REMOVING OF COMMANDS IN SOURCE
21 * ...ONCE PROJECT ACTIONS ARE ALL DONE... intertwine with actions... as well as command filtering
22 *
23 * Implements typical word processor key stroke behaviour in the web browser
24 */
25bootstrap.provides("Typing");
26
27/**
28 * The typing singleton is just a model which fires a single event:
29 * onTyping, which passes three arguments:
30 * typingEvent: An object containing a cancel member, when flagged as true then the typing module will
31 * not execute typing logic.
32 *
33 * event: The DOM event
34 *
35 * normalizedKey: The string containing the normalized key
36 *
37 * This is called in a keystroke event on the document.
38 *
39 * @see _addHandler
40 */
41
42de.Typing = {}; // Model
43
44(function(){
45
46 $enqueueInit("Typing", function() {
47 _addHandler(document, "keystroke", onKeyStroke);
48 _model(de.Typing);
49 }, "MVC");
50
51 function onKeyStroke(e, normalizedKey) {
52
53 debug.println("Key-Stroke: '" + normalizedKey + "'");
54
55 // Fire typing event
56 var typingEvent = {cancel:false};
57 de.Typing.fireEvent("Typing", typingEvent, e, normalizedKey);
58
59 // Cancel event if requested
60 if (typingEvent.cancel)
61 return;
62
63 // Is the CTRL (or Apple-func key for mac) down?
64 if (de.events.Keyboard.isAcceleratorDown(e)) {
65
66 switch (normalizedKey.toLowerCase()) {
67 case "z":
68 de.UndoMan.undo();
69 return false;
70
71 case "y":
72 de.UndoMan.redo();
73 return false;
74 }
75
76 }
77
78 // Don't manipulate the selection if it is not editable
79 if (!de.selection.isRangeEditable() || !de.cursor.exists())
80 return;
81
82 var targetES = de.doc.getEditSectionContainer(de.cursor.getCurrentCursorDesc().domNode);
83
84 // Is the CTRL (or Apple-func key for mac) down?
85 if (de.events.Keyboard.isAcceleratorDown(e)) {
86
87 switch (normalizedKey.toLowerCase()) {
88
89 case "b":
90 toggleFormat("bold");
91 return false;
92
93 case "i":
94 toggleFormat("italics");
95 return false;
96
97 case "u":
98 toggleFormat("underline");
99 return false;
100
101 case "a": // Select all
102 de.selection.selectAll(targetES);
103 return false;
104
105
106 }
107
108 } else if (!e.ctrlKey && !e.metaKey && !e.altKey) {
109
110 var keyStr = normalizedKey;
111
112 if (keyStr) {
113
114 if (keyStr.length > 1) {
115
116 switch (keyStr) {
117 case "Space":
118 keyStr = " ";
119 break;
120
121 case "Tab": // Indentation / promotion
122
123 if (_isBlockLevel(targetES) || targetES == docBody) {
124
125 var firstBlock = _findAncestor(de.cursor.getCurrentCursorDesc().domNode, docBody, _isBlockLevel, 1);
126
127 if (firstBlock && _nodeName(firstBlock) == "li") {
128
129 if (e.shiftKey)
130 de.UndoMan.execute("DemoteItem");
131 else de.UndoMan.execute("PromoteItem");
132
133 } else de.UndoMan.execute("Indent", !e.shiftKey);
134
135
136 }
137
138 return false;
139
140 case "Delete":
141 case "Backspace":
142
143 if (de.selection.remove()) {
144 return false;
145
146 } else {
147
148 // Get the cursor descriptor
149 var cursorDesc = de.cursor.getCurrentCursorDesc(),
150 preDesc,
151 postDesc;
152
153 if (keyStr == "Backspace") {
154
155 if (cursorDesc.isRightOf && (cursorDesc.placement == (de.cursor.PlacementFlag.AFTER | de.cursor.PlacementFlag.BEFORE))) {
156 // Delete before/after nodes completely if cursor is directly after them
157 preDesc = _clone(cursorDesc);
158 preDesc.isRightOf = false;
159 } else {
160 preDesc = de.cursor.getNextCursorMovement(cursorDesc, true);
161 }
162
163 postDesc = cursorDesc;
164
165 } else {
166 // Delete is just like backspace on the right charactor.
167 preDesc = cursorDesc;
168
169 if (!cursorDesc.isRightOf && (cursorDesc.placement == (de.cursor.PlacementFlag.AFTER | de.cursor.PlacementFlag.BEFORE))) {
170 // Delete before/after nodes completely if cursor is directly after them
171 postDesc = _clone(cursorDesc);
172 postDesc.isRightOf = true;
173 } else {
174 postDesc = de.cursor.getNextCursorMovement(cursorDesc, false);
175 }
176
177 }
178
179 if (preDesc && postDesc) {
180
181 // Check that the range is valid
182 var isEditable = de.doc.isNodeEditable(postDesc.domNode);
183 if (isEditable && postDesc.domNode != preDesc.domNode) isEditable &= de.doc.isNodeEditable(preDesc.domNode);
184
185 if (isEditable) {
186
187 var preIndex;
188 if (preDesc.domNode.nodeType == Node.TEXT_NODE) {
189 preIndex = preDesc.relIndex;
190 if (preDesc.isRightOf) preIndex++;
191 } else {
192
193 if (_nodeName(preDesc.domNode) == "br") {
194
195 // If the pre desc is a line break, then count how many other line breaks there are between the pre/post desc
196 var brCount = 0;
197 _visitNodes(_getCommonAncestor(preDesc.domNode, postDesc.domNode), preDesc.domNode, true, null, function(domNode){
198 if (_nodeName(domNode) == "br") brCount++;
199 return domNode != postDesc.domNode;
200 });
201
202 // If there are more than one line breaks, then exclude the starting line break... since only
203 // one line break should be removed
204 preIndex = brCount > 1 ? 1 : 0;
205
206 } else
207 preIndex = preDesc.isRightOf ? 1 : 0;
208 }
209
210 var postIndex;
211 if (postDesc.domNode.nodeType == Node.TEXT_NODE) {
212 postIndex = postDesc.relIndex;
213 if (postDesc.isRightOf) postIndex++;
214 } else {
215
216 // If the pre desc is a line break, then count how many other line breaks there are between the pre/post desc
217 if (_nodeName(postDesc.domNode) == "br") {
218 var brCount = 0;
219 _visitNodes(_getCommonAncestor(preDesc.domNode, postDesc.domNode), preDesc.domNode, true, null, function(domNode){
220 if (_nodeName(domNode) == "br" && !(domNode == preDesc.domNode && preIndex == 1)) brCount++;
221
222 return domNode != postDesc.domNode;
223 });
224
225 // If there are more than one line breaks, then exclude the ending line break... since only
226 // one line break should be removed
227 postIndex = brCount > 1 ? 0 : 1;
228
229 } else
230 postIndex = postDesc.isRightOf ? 1 : 0;
231
232 }
233
234 // Get virtual range since cursor node/index may probably be affected by the selection
235 var preVNI = de.selection.getVirtualNodeIndex(preDesc.domNode, preIndex),
236 postVNI = de.selection.getVirtualNodeIndex(postDesc.domNode, postIndex);
237
238
239 // See if can use light-weight remove text action
240 if (preVNI.node == postVNI.node && preVNI.node.nodeType == Node.TEXT_NODE)
241 de.UndoMan.execute("RemoveText", preVNI.node, preVNI.index, postVNI.index - preVNI.index);
242 else {
243 // Check to see that the range does not remove any editable sections
244 var ca = _getCommonAncestor(preVNI.node, postVNI.node, true);
245 if (de.doc.isEditSection(ca) || de.doc.isNodeEditable(ca))
246 de.UndoMan.execute("RemoveDOM", preVNI.node, preVNI.index, postVNI.node, postVNI.index);
247 }
248 }
249 }
250
251 // signal that no further event processing from further up the event stack is needed
252 return false;
253
254 }
255
256 case "Enter":
257
258 de.selection.remove();
259 var cursorDesc = de.cursor.getCurrentCursorDesc();
260 debug.assert(cursorDesc != null);
261
262 var index = cursorDesc.relIndex;
263 if (cursorDesc.domNode.nodeType == Node.TEXT_NODE)
264 index += (cursorDesc.isRightOf ? 1 : 0);
265 else index = cursorDesc.isRightOf ? 1 : 0;
266
267 if (!de.doc.getEditProperties(targetES).singleLine) {
268 if (e.shiftKey) {
269
270 var lbHTML = "<br>";
271 // Line breaks may need place holders too
272 if (!(cursorDesc.domNode.nodeType == Node.TEXT_NODE &&
273 index < _nodeLength(cursorDesc.domNode))) lbHTML += _getOuterHTML(de.doc.createMNPlaceholder());
274
275 de.UndoMan.execute("InsertHTML", lbHTML, cursorDesc.domNode.parentNode, cursorDesc.domNode, index);
276
277 } else if (_isBlockLevel(targetES) || targetES == docBody) {
278 // Should never allow block container to be created in inline editable sections! (Invaid HTML)
279 de.UndoMan.execute("SplitContainer", cursorDesc.domNode, index);
280 }
281 }
282
283 return false;
284
285 case "Home":
286 case "End":
287
288 // Set the cursor to either the beggining or end of the current editable section.
289
290 var isHome = keyStr == "Home",
291 boundDesc = de.cursor.getNearestCursorDesc(targetES, isHome ? 0 : 1, !isHome, !isHome);
292
293 if (boundDesc) {
294 de.cursor.setCursor(boundDesc);
295 de.cursor.scrollToCursor();
296 return false;
297 }
298
299 default:
300 return true;
301
302 }
303 }
304
305 // The key press is a printable charactor.
306
307 // Remove any selection there might be
308 de.selection.remove();
309
310 // Get the cursor descriptor
311 var cursorDesc = de.cursor.getCurrentCursorDesc();
312 debug.assert(cursorDesc != null);
313
314 // Calculate the cursor index
315 var index = cursorDesc.relIndex;
316 if (cursorDesc.domNode.nodeType == Node.TEXT_NODE && cursorDesc.isRightOf)
317 index++;
318 else if (cursorDesc.domNode.nodeType == Node.ELEMENT_NODE)
319 index = cursorDesc.isRightOf ? 1 : 0;
320
321 // Perform a text insert action via the undo manager
322 de.UndoMan.execute("InsertText", cursorDesc.domNode, keyStr, index);
323
324 return false;
325
326 }
327
328
329 }
330
331 }
332
333 /**
334 *
335 * @param {String} formatType
336 * @return {Boolean} True if something was formatted, false if not
337 */
338 function toggleFormat(formatType) {
339 // Perform the action via the undo manager
340 return de.UndoMan.execute("Format", formatType, !de.selection.getEditState([formatType]).formatStates[formatType]) ? true : false;
341 }
342
343
344})();
Note: See TracBrowser for help on using the repository browser.