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