Reviewed by Ken.
[WebKit-https.git] / WebCore / khtml / editing / jsediting.cpp
1 /*
2  * Copyright (C) 2004 Apple Computer, Inc.  All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions
6  * are met:
7  * 1. Redistributions of source code must retain the above copyright
8  *    notice, this list of conditions and the following disclaimer.
9  * 2. Redistributions in binary form must reproduce the above copyright
10  *    notice, this list of conditions and the following disclaimer in the
11  *    documentation and/or other materials provided with the distribution.
12  *
13  * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
14  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
15  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE COMPUTER, INC. OR
17  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
18  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
19  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
20  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
21  * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
23  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24  */
25
26 #include "jsediting.h"
27
28 #include "cssproperties.h"
29 #include "dom_selection.h"
30 #include "htmlediting.h"
31 #include "khtml_part.h"
32 #include "KWQKHTMLPart.h"
33 #include "qstring.h"
34
35 using khtml::TypingCommand;
36
37 namespace DOM {
38
39 class DocumentImpl;
40
41 namespace {
42
43 struct CommandImp {
44     bool (*execFn)(KHTMLPart *part, bool userInterface, const DOMString &value);
45     bool (*enabledFn)(KHTMLPart *part);
46     KHTMLPart::TriState (*stateFn)(KHTMLPart *part);
47     DOMString (*valueFn)(KHTMLPart *part);
48 };
49
50 QDict<CommandImp> createCommandDictionary();
51
52 const CommandImp *commandImp(const DOMString &command)
53 {
54     static QDict<CommandImp> commandDictionary = createCommandDictionary();
55     return commandDictionary.find(command.string());
56 }
57
58 } // anonymous namespace
59
60 bool JSEditor::execCommand(const DOMString &command, bool userInterface, const DOMString &value)
61 {
62     const CommandImp *cmd = commandImp(command);
63     if (!cmd)
64         return false;
65     KHTMLPart *part = m_doc->part();
66     if (!part)
67         return false;
68     m_doc->updateLayout();
69     return cmd->enabledFn(part) && cmd->execFn(part, userInterface, value);
70 }
71
72 bool JSEditor::queryCommandEnabled(const DOMString &command)
73 {
74     const CommandImp *cmd = commandImp(command);
75     if (!cmd)
76         return false;
77     KHTMLPart *part = m_doc->part();
78     if (!part)
79         return false;
80     m_doc->updateLayout();
81     return cmd->enabledFn(part);
82 }
83
84 bool JSEditor::queryCommandIndeterm(const DOMString &command)
85 {
86     const CommandImp *cmd = commandImp(command);
87     if (!cmd)
88         return false;
89     KHTMLPart *part = m_doc->part();
90     if (!part)
91         return false;
92     m_doc->updateLayout();
93     return cmd->stateFn(part) == KHTMLPart::mixedTriState;
94 }
95
96 bool JSEditor::queryCommandState(const DOMString &command)
97 {
98     const CommandImp *cmd = commandImp(command);
99     if (!cmd)
100         return false;
101     KHTMLPart *part = m_doc->part();
102     if (!part)
103         return false;
104     m_doc->updateLayout();
105     return cmd->stateFn(part) != KHTMLPart::falseTriState;
106 }
107
108 bool JSEditor::queryCommandSupported(const DOMString &command)
109 {
110     return commandImp(command) != 0;
111 }
112
113 DOMString JSEditor::queryCommandValue(const DOMString &command)
114 {
115     const CommandImp *cmd = commandImp(command);
116     if (!cmd)
117         return DOMString();
118     KHTMLPart *part = m_doc->part();
119     if (!part)
120         return DOMString();
121     m_doc->updateLayout();
122     return cmd->valueFn(part);
123 }
124
125 // =============================================================================================
126
127 // Private stuff, all inside an anonymous namespace.
128
129 namespace {
130
131 bool execStyleChange(KHTMLPart *part, int propertyID, const DOMString &propertyValue)
132 {
133     CSSStyleDeclarationImpl *style = new CSSStyleDeclarationImpl(0);
134     style->setProperty(propertyID, propertyValue);
135     style->ref();
136     part->applyStyle(style);
137     style->deref();
138     return true;
139 }
140
141 bool execStyleChange(KHTMLPart *part, int propertyID, const char *propertyValue)
142 {
143     return execStyleChange(part, propertyID, DOMString(propertyValue));
144 }
145
146 KHTMLPart::TriState stateStyle(KHTMLPart *part, int propertyID, const char *desiredValue)
147 {
148     CSSStyleDeclarationImpl *style = new CSSStyleDeclarationImpl(0);
149     style->setProperty(propertyID, desiredValue);
150     style->ref();
151     KHTMLPart::TriState state = part->selectionHasStyle(style);
152     style->deref();
153     return state;
154 }
155
156 bool selectionStartHasStyle(KHTMLPart *part, int propertyID, const char *desiredValue)
157 {
158     CSSStyleDeclarationImpl *style = new CSSStyleDeclarationImpl(0);
159     style->setProperty(propertyID, desiredValue);
160     style->ref();
161     bool hasStyle = part->selectionStartHasStyle(style);
162     style->deref();
163     return hasStyle;
164 }
165
166 DOMString valueStyle(KHTMLPart *part, int propertyID)
167 {
168     return part->selectionStartStylePropertyValue(propertyID);
169 }
170
171 // =============================================================================================
172 //
173 // execCommand implementations
174 //
175
176 bool execBackColor(KHTMLPart *part, bool userInterface, const DOMString &value)
177 {
178     return execStyleChange(part, CSS_PROP_BACKGROUND_COLOR, value);
179 }
180
181 bool execBold(KHTMLPart *part, bool userInterface, const DOMString &value)
182 {
183     bool isBold = selectionStartHasStyle(part, CSS_PROP_FONT_WEIGHT, "bold");
184     return execStyleChange(part, CSS_PROP_FONT_WEIGHT, isBold ? "normal" : "bold");
185 }
186
187 bool execCopy(KHTMLPart *part, bool userInterface, const DOMString &value)
188 {
189     part->copyToPasteboard();
190     return true;
191 }
192
193 bool execCut(KHTMLPart *part, bool userInterface, const DOMString &value)
194 {
195     part->cutToPasteboard();
196     return true;
197 }
198
199 bool execDelete(KHTMLPart *part, bool userInterface, const DOMString &value)
200 {
201     TypingCommand::deleteKeyPressed(part->xmlDocImpl());
202     return true;
203 }
204
205 bool execFontName(KHTMLPart *part, bool userInterface, const DOMString &value)
206 {
207     return execStyleChange(part, CSS_PROP_FONT_FAMILY, value);
208 }
209
210 bool execFontSize(KHTMLPart *part, bool userInterface, const DOMString &value)
211 {
212     return execStyleChange(part, CSS_PROP_FONT_SIZE, value);
213 }
214
215 bool execForeColor(KHTMLPart *part, bool userInterface, const DOMString &value)
216 {
217     return execStyleChange(part, CSS_PROP_COLOR, value);
218 }
219
220 bool execIndent(KHTMLPart *part, bool userInterface, const DOMString &value)
221 {
222     // FIXME: Implement.
223     return false;
224 }
225
226 bool execInsertNewline(KHTMLPart *part, bool userInterface, const DOMString &value)
227 {
228     TypingCommand::insertNewline(part->xmlDocImpl());
229     return true;
230 }
231
232 bool execInsertParagraph(KHTMLPart *part, bool userInterface, const DOMString &value)
233 {
234     // FIXME: Implement.
235     return false;
236 }
237
238 bool execInsertText(KHTMLPart *part, bool userInterface, const DOMString &value)
239 {
240     TypingCommand::insertText(part->xmlDocImpl(), value);
241     return true;
242 }
243
244 bool execItalic(KHTMLPart *part, bool userInterface, const DOMString &value)
245 {
246     bool isItalic = selectionStartHasStyle(part, CSS_PROP_FONT_STYLE, "italic");
247     return execStyleChange(part, CSS_PROP_FONT_STYLE, isItalic ? "normal" : "italic");
248 }
249
250 bool execJustifyCenter(KHTMLPart *part, bool userInterface, const DOMString &value)
251 {
252     return execStyleChange(part, CSS_PROP_TEXT_ALIGN, "center");
253 }
254
255 bool execJustifyFull(KHTMLPart *part, bool userInterface, const DOMString &value)
256 {
257     return execStyleChange(part, CSS_PROP_TEXT_ALIGN, "justify");
258 }
259
260 bool execJustifyLeft(KHTMLPart *part, bool userInterface, const DOMString &value)
261 {
262     return execStyleChange(part, CSS_PROP_TEXT_ALIGN, "left");
263 }
264
265 bool execJustifyRight(KHTMLPart *part, bool userInterface, const DOMString &value)
266 {
267     return execStyleChange(part, CSS_PROP_TEXT_ALIGN, "right");
268 }
269
270 bool execOutdent(KHTMLPart *part, bool userInterface, const DOMString &value)
271 {
272     // FIXME: Implement.
273     return false;
274 }
275
276 #if SUPPORT_PASTE
277
278 bool execPaste(KHTMLPart *part, bool userInterface, const DOMString &value)
279 {
280     part->pasteFromPasteboard();
281     return true;
282 }
283
284 #endif
285
286 bool execPrint(KHTMLPart *part, bool userInterface, const DOMString &value)
287 {
288     part->print();
289     return true;
290 }
291
292 bool execRedo(KHTMLPart *part, bool userInterface, const DOMString &value)
293 {
294     part->redo();
295     return true;
296 }
297
298 bool execSelectAll(KHTMLPart *part, bool userInterface, const DOMString &value)
299 {
300     part->selectAll();
301     return true;
302 }
303
304 bool execSubscript(KHTMLPart *part, bool userInterface, const DOMString &value)
305 {
306     return execStyleChange(part, CSS_PROP_VERTICAL_ALIGN, "sub");
307 }
308
309 bool execSuperscript(KHTMLPart *part, bool userInterface, const DOMString &value)
310 {
311     return execStyleChange(part, CSS_PROP_VERTICAL_ALIGN, "super");
312 }
313
314 bool execUndo(KHTMLPart *part, bool userInterface, const DOMString &value)
315 {
316     part->undo();
317     return true;
318 }
319
320 bool execUnselect(KHTMLPart *part, bool userInterface, const DOMString &value)
321 {
322     part->clearSelection();
323     return true;
324 }
325
326 // =============================================================================================
327 //
328 // queryCommandEnabled implementations
329 //
330 // It's a bit difficult to get a clear notion of the difference between
331 // "supported" and "enabled" from reading the Microsoft documentation, but
332 // what little I could glean from that seems to make some sense.
333 //     Supported = The command is supported by this object.
334 //     Enabled =   The command is available and enabled.
335
336 bool enabled(KHTMLPart *part)
337 {
338     return true;
339 }
340
341 bool enabledAnySelection(KHTMLPart *part)
342 {
343     return part->selection().notEmpty();
344 }
345
346 #if SUPPORT_PASTE
347
348 bool enabledPaste(KHTMLPart *part)
349 {
350     return part->canPaste();
351 }
352
353 #endif
354
355 bool enabledRangeSelection(KHTMLPart *part)
356 {
357     return part->selection().state() == Selection::RANGE;
358 }
359
360 bool enabledRedo(KHTMLPart *part)
361 {
362     return part->canRedo();
363 }
364
365 bool enabledUndo(KHTMLPart *part)
366 {
367     return part->canUndo();
368 }
369
370 // =============================================================================================
371 //
372 // queryCommandIndeterm/State implementations
373 //
374 // It's a bit difficult to get a clear notion of what these methods are supposed
375 // to do from reading the Microsoft documentation, but my current guess is this:
376 //
377 //     queryCommandState and queryCommandIndeterm work in concert to return
378 //     the two bits of information that are needed to tell, for instance,
379 //     if the text of a selection is bold. The answer can be "yes", "no", or
380 //     "partially".
381 //
382 // If this is so, then queryCommandState should return "yes" in the case where
383 // all the text is bold and "no" for non-bold or partially-bold text.
384 // Then, queryCommandIndeterm should return "no" in the case where
385 // all the text is either all bold or not-bold and and "yes" for partially-bold text.
386
387 KHTMLPart::TriState stateNone(KHTMLPart *part)
388 {
389     return KHTMLPart::falseTriState;
390 }
391
392 KHTMLPart::TriState stateBold(KHTMLPart *part)
393 {
394     return stateStyle(part, CSS_PROP_FONT_WEIGHT, "bold");
395 }
396
397 KHTMLPart::TriState stateItalic(KHTMLPart *part)
398 {
399     return stateStyle(part, CSS_PROP_FONT_STYLE, "italic");
400 }
401
402 KHTMLPart::TriState stateSubscript(KHTMLPart *part)
403 {
404     return stateStyle(part, CSS_PROP_VERTICAL_ALIGN, "sub");
405 }
406
407 KHTMLPart::TriState stateSuperscript(KHTMLPart *part)
408 {
409     return stateStyle(part, CSS_PROP_VERTICAL_ALIGN, "super");
410 }
411
412 // =============================================================================================
413 //
414 // queryCommandValue implementations
415 //
416
417 DOMString valueNull(KHTMLPart *part)
418 {
419     return DOMString();
420 }
421
422 DOMString valueBackColor(KHTMLPart *part)
423 {
424     return valueStyle(part, CSS_PROP_BACKGROUND_COLOR);
425 }
426
427 DOMString valueFontName(KHTMLPart *part)
428 {
429     return valueStyle(part, CSS_PROP_FONT_FAMILY);
430 }
431
432 DOMString valueFontSize(KHTMLPart *part)
433 {
434     return valueStyle(part, CSS_PROP_FONT_SIZE);
435 }
436
437 DOMString valueForeColor(KHTMLPart *part)
438 {
439     return valueStyle(part, CSS_PROP_COLOR);
440 }
441
442 // =============================================================================================
443
444 QDict<CommandImp> createCommandDictionary()
445 {
446     struct EditorCommand { const char *name; CommandImp imp; };
447
448     static const EditorCommand commands[] = {
449
450         { "BackColor", { execBackColor, enabled, stateNone, valueBackColor } },
451         { "Bold", { execBold, enabledAnySelection, stateBold, valueNull } },
452         { "Copy", { execCopy, enabledRangeSelection, stateNone, valueNull } },
453         { "Cut", { execCut, enabledRangeSelection, stateNone, valueNull } },
454         { "Delete", { execDelete, enabledAnySelection, stateNone, valueNull } },
455         { "FontName", { execFontName, enabledAnySelection, stateNone, valueFontName } },
456         { "FontSize", { execFontSize, enabledAnySelection, stateNone, valueFontSize } },
457         { "ForeColor", { execForeColor, enabledAnySelection, stateNone, valueForeColor } },
458         { "Indent", { execIndent, enabledAnySelection, stateNone, valueNull } },
459         { "InsertNewline", { execInsertNewline, enabledAnySelection, stateNone, valueNull } },
460         { "InsertParagraph", { execInsertParagraph, enabledAnySelection, stateNone, valueNull } },
461         { "InsertText", { execInsertText, enabledAnySelection, stateNone, valueNull } },
462         { "Italic", { execItalic, enabledAnySelection, stateItalic, valueNull } },
463         { "JustifyCenter", { execJustifyCenter, enabledAnySelection, stateNone, valueNull } },
464         { "JustifyFull", { execJustifyFull, enabledAnySelection, stateNone, valueNull } },
465         { "JustifyLeft", { execJustifyLeft, enabledAnySelection, stateNone, valueNull } },
466         { "JustifyNone", { execJustifyLeft, enabledAnySelection, stateNone, valueNull } },
467         { "JustifyRight", { execJustifyRight, enabledAnySelection, stateNone, valueNull } },
468         { "Outdent", { execOutdent, enabledAnySelection, stateNone, valueNull } },
469 #if SUPPORT_PASTE
470         { "Paste", { execPaste, enabledPaste, stateNone, valueNull } },
471 #endif
472         { "Print", { execPrint, enabled, stateNone, valueNull } },
473         { "Redo", { execRedo, enabledRedo, stateNone, valueNull } },
474         { "SelectAll", { execSelectAll, enabled, stateNone, valueNull } },
475         { "Subscript", { execSubscript, enabledAnySelection, stateSubscript, valueNull } },
476         { "Superscript", { execSuperscript, enabledAnySelection, stateSuperscript, valueNull } },
477         { "Undo", { execUndo, enabledUndo, stateNone, valueNull } },
478         { "Unselect", { execUnselect, enabledAnySelection, stateNone, valueNull } }
479
480         //
481         // The "unsupported" commands are listed here since they appear in the Microsoft
482         // documentation used as the basis for the list.
483         //
484
485         // 2d-position (not supported)
486         // AbsolutePosition (not supported)
487         // BlockDirLTR (not supported)
488         // BlockDirRTL (not supported)
489         // BrowseMode (not supported)
490         // ClearAuthenticationCache (not supported)
491         // CreateBookmark (not supported)
492         // CreateLink (not supported)
493         // DirLTR (not supported)
494         // DirRTL (not supported)
495         // EditMode (not supported)
496         // FormatBlock (not supported)
497         // InlineDirLTR (not supported)
498         // InlineDirRTL (not supported)
499         // InsertButton (not supported)
500         // InsertFieldSet (not supported)
501         // InsertHorizontalRule (not supported)
502         // InsertIFrame (not supported)
503         // InsertImage (not supported)
504         // InsertInputButton (not supported)
505         // InsertInputCheckbox (not supported)
506         // InsertInputFileUpload (not supported)
507         // InsertInputHidden (not supported)
508         // InsertInputImage (not supported)
509         // InsertInputPassword (not supported)
510         // InsertInputRadio (not supported)
511         // InsertInputReset (not supported)
512         // InsertInputSubmit (not supported)
513         // InsertInputText (not supported)
514         // InsertMarquee (not supported)
515         // InsertOrderedList (not supported)
516         // InsertSelectDropDown (not supported)
517         // InsertSelectListBox (not supported)
518         // InsertTextArea (not supported)
519         // InsertUnorderedList (not supported)
520         // LiveResize (not supported)
521         // MultipleSelection (not supported)
522         // Open (not supported)
523         // Overwrite (not supported)
524         // PlayImage (not supported)
525         // Refresh (not supported)
526         // RemoveFormat (not supported)
527         // RemoveParaFormat (not supported)
528         // SaveAs (not supported)
529         // SizeToControl (not supported)
530         // SizeToControlHeight (not supported)
531         // SizeToControlWidth (not supported)
532         // Stop (not supported)
533         // Stopimage (not supported)
534         // Strikethrough (not supported)
535         // Unbookmark (not supported)
536         // Underline (not supported)
537         // Unlink (not supported)
538     };
539
540     const int numCommands = sizeof(commands) / sizeof(commands[0]);
541     QDict<CommandImp> commandDictionary(numCommands, false); // case-insensitive dictionary
542     for (int i = 0; i < numCommands; ++i) {
543         commandDictionary.insert(commands[i].name, &commands[i].imp);
544     }
545     return commandDictionary;
546 }
547
548 } // anonymous namespace
549
550 } // namespace DOM