2009-04-24 Diego Escalante Urrelo <diegoe@gnome.org>
[WebKit-https.git] / WebKit / gtk / WebCoreSupport / EditorClientGtk.cpp
1 /*
2  *  Copyright (C) 2007 Alp Toker <alp@atoker.com>
3  *  Copyright (C) 2008 Nuanti Ltd.
4  *  Copyright (C) 2009 Diego Escalante Urrelo <diegoe@gnome.org>
5  *
6  *  This library is free software; you can redistribute it and/or
7  *  modify it under the terms of the GNU Lesser General Public
8  *  License as published by the Free Software Foundation; either
9  *  version 2 of the License, or (at your option) any later version.
10  *
11  *  This library is distributed in the hope that it will be useful,
12  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
13  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  *  Lesser General Public License for more details.
15  *
16  *  You should have received a copy of the GNU Lesser General Public
17  *  License along with this library; if not, write to the Free Software
18  *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
19  */
20
21 #include "config.h"
22 #include "EditorClientGtk.h"
23
24 #include "CString.h"
25 #include "EditCommand.h"
26 #include "Editor.h"
27 #include <enchant.h>
28 #include "FocusController.h"
29 #include "Frame.h"
30 #include <glib.h>
31 #include "KeyboardCodes.h"
32 #include "KeyboardEvent.h"
33 #include "NotImplemented.h"
34 #include "Page.h"
35 #include "PlatformKeyboardEvent.h"
36 #include "markup.h"
37 #include "webkitprivate.h"
38
39 using namespace WebCore;
40
41 namespace WebKit {
42
43 static void imContextCommitted(GtkIMContext* context, const gchar* str, EditorClient* client)
44 {
45     Frame* targetFrame = core(client->m_webView)->focusController()->focusedOrMainFrame();
46
47     if (!targetFrame || !targetFrame->editor()->canEdit())
48         return;
49
50     Editor* editor = targetFrame->editor();
51
52     String commitString = String::fromUTF8(str);
53     editor->confirmComposition(commitString);
54 }
55
56 static void imContextPreeditChanged(GtkIMContext* context, EditorClient* client)
57 {
58     Frame* frame = core(client->m_webView)->focusController()->focusedOrMainFrame();
59     Editor* editor = frame->editor();
60
61     gchar* preedit = NULL;
62     gint cursorPos = 0;
63     // We ignore the provided PangoAttrList for now.
64     gtk_im_context_get_preedit_string(context, &preedit, NULL, &cursorPos);
65     String preeditString = String::fromUTF8(preedit);
66     g_free(preedit);
67
68     // setComposition() will replace the user selection if passed an empty
69     // preedit. We don't want this to happen.
70     if (preeditString.isEmpty() && !editor->hasComposition())
71         return;
72
73     Vector<CompositionUnderline> underlines;
74     underlines.append(CompositionUnderline(0, preeditString.length(), Color(0, 0, 0), false));
75     editor->setComposition(preeditString, underlines, cursorPos, 0);
76 }
77
78 void EditorClient::setInputMethodState(bool active)
79 {
80     WebKitWebViewPrivate* priv = m_webView->priv;
81
82     if (active)
83         gtk_im_context_focus_in(priv->imContext);
84     else
85         gtk_im_context_focus_out(priv->imContext);
86
87 #ifdef MAEMO_CHANGES
88     if (active)
89         hildon_gtk_im_context_show(priv->imContext);
90     else
91         hildon_gtk_im_context_hide(priv->imContext);
92 #endif
93 }
94
95 bool EditorClient::shouldDeleteRange(Range*)
96 {
97     notImplemented();
98     return true;
99 }
100
101 bool EditorClient::shouldShowDeleteInterface(HTMLElement*)
102 {
103     return false;
104 }
105
106 bool EditorClient::isContinuousSpellCheckingEnabled()
107 {
108     WebKitWebSettings* settings = webkit_web_view_get_settings(m_webView);
109
110     gboolean enabled;
111     g_object_get(settings, "enable-spell-checking", &enabled, NULL);
112
113     return enabled;
114 }
115
116 bool EditorClient::isGrammarCheckingEnabled()
117 {
118     notImplemented();
119     return false;
120 }
121
122 int EditorClient::spellCheckerDocumentTag()
123 {
124     notImplemented();
125     return 0;
126 }
127
128 bool EditorClient::shouldBeginEditing(WebCore::Range*)
129 {
130     notImplemented();
131     return true;
132 }
133
134 bool EditorClient::shouldEndEditing(WebCore::Range*)
135 {
136     notImplemented();
137     return true;
138 }
139
140 bool EditorClient::shouldInsertText(const String&, Range*, EditorInsertAction)
141 {
142     notImplemented();
143     return true;
144 }
145
146 bool EditorClient::shouldChangeSelectedRange(Range*, Range*, EAffinity, bool)
147 {
148     notImplemented();
149     return true;
150 }
151
152 bool EditorClient::shouldApplyStyle(WebCore::CSSStyleDeclaration*, WebCore::Range*)
153 {
154     notImplemented();
155     return true;
156 }
157
158 bool EditorClient::shouldMoveRangeAfterDelete(WebCore::Range*, WebCore::Range*)
159 {
160     notImplemented();
161     return true;
162 }
163
164 void EditorClient::didBeginEditing()
165 {
166     notImplemented();
167 }
168
169 void EditorClient::respondToChangedContents()
170 {
171     notImplemented();
172 }
173
174 static void clipboard_get_contents_cb(GtkClipboard* clipboard, GtkSelectionData* selection_data, guint info, gpointer data)
175 {
176     WebKitWebView* webView = reinterpret_cast<WebKitWebView*>(data);
177     Frame* frame = core(webView)->focusController()->focusedOrMainFrame();
178     PassRefPtr<Range> selectedRange = frame->selection()->toNormalizedRange();
179
180     if (static_cast<gint>(info) == WEBKIT_WEB_VIEW_TARGET_INFO_HTML) {
181         String markup = createMarkup(selectedRange.get(), 0, AnnotateForInterchange);
182         gtk_selection_data_set(selection_data, selection_data->target, 8,
183                                reinterpret_cast<const guchar*>(markup.utf8().data()), markup.utf8().length());
184     } else {
185         String text = selectedRange->text();
186         gtk_selection_data_set_text(selection_data, text.utf8().data(), text.utf8().length());
187     }
188 }
189
190 static void clipboard_clear_contents_cb(GtkClipboard* clipboard, gpointer data)
191 {
192     WebKitWebView* webView = reinterpret_cast<WebKitWebView*>(data);
193     Frame* frame = core(webView)->focusController()->focusedOrMainFrame();
194
195     // Collapse the selection without clearing it
196     frame->selection()->setBase(frame->selection()->extent(), frame->selection()->affinity());
197 }
198
199 void EditorClient::respondToChangedSelection()
200 {
201     WebKitWebViewPrivate* priv = m_webView->priv;
202     Frame* targetFrame = core(m_webView)->focusController()->focusedOrMainFrame();
203
204     if (!targetFrame)
205         return;
206
207     if (targetFrame->editor()->ignoreCompositionSelectionChange())
208         return;
209
210     GtkClipboard* clipboard = gtk_widget_get_clipboard(GTK_WIDGET(m_webView), GDK_SELECTION_PRIMARY);
211     if (targetFrame->selection()->isRange()) {
212         GtkTargetList* targetList = webkit_web_view_get_copy_target_list(m_webView);
213         gint targetCount;
214         GtkTargetEntry* targets = gtk_target_table_new_from_list(targetList, &targetCount);
215         gtk_clipboard_set_with_owner(clipboard, targets, targetCount,
216                                      clipboard_get_contents_cb, clipboard_clear_contents_cb, G_OBJECT(m_webView));
217         gtk_target_table_free(targets, targetCount);
218     } else if (gtk_clipboard_get_owner(clipboard) == G_OBJECT(m_webView))
219         gtk_clipboard_clear(clipboard);
220
221     if (!targetFrame->editor()->hasComposition())
222         return;
223
224     unsigned start;
225     unsigned end;
226     if (!targetFrame->editor()->getCompositionSelection(start, end)) {
227         // gtk_im_context_reset() clears the composition for us.
228         gtk_im_context_reset(priv->imContext);
229         targetFrame->editor()->confirmCompositionWithoutDisturbingSelection();
230     }
231 }
232
233 void EditorClient::didEndEditing()
234 {
235     notImplemented();
236 }
237
238 void EditorClient::didWriteSelectionToPasteboard()
239 {
240     notImplemented();
241 }
242
243 void EditorClient::didSetSelectionTypesForPasteboard()
244 {
245     notImplemented();
246 }
247
248 bool EditorClient::isEditable()
249 {
250     return webkit_web_view_get_editable(m_webView);
251 }
252
253 void EditorClient::registerCommandForUndo(WTF::PassRefPtr<WebCore::EditCommand>)
254 {
255     notImplemented();
256 }
257
258 void EditorClient::registerCommandForRedo(WTF::PassRefPtr<WebCore::EditCommand>)
259 {
260     notImplemented();
261 }
262
263 void EditorClient::clearUndoRedoOperations()
264 {
265     notImplemented();
266 }
267
268 bool EditorClient::canUndo() const
269 {
270     notImplemented();
271     return false;
272 }
273
274 bool EditorClient::canRedo() const
275 {
276     notImplemented();
277     return false;
278 }
279
280 void EditorClient::undo()
281 {
282     notImplemented();
283 }
284
285 void EditorClient::redo()
286 {
287     notImplemented();
288 }
289
290 bool EditorClient::shouldInsertNode(Node*, Range*, EditorInsertAction)
291 {
292     notImplemented();
293     return true;
294 }
295
296 void EditorClient::pageDestroyed()
297 {
298     delete this;
299 }
300
301 bool EditorClient::smartInsertDeleteEnabled()
302 {
303     notImplemented();
304     return false;
305 }
306
307 bool EditorClient::isSelectTrailingWhitespaceEnabled()
308 {
309     notImplemented();
310     return false;
311 }
312
313 void EditorClient::toggleContinuousSpellChecking()
314 {
315     WebKitWebSettings* settings = webkit_web_view_get_settings(m_webView);
316
317     gboolean enabled;
318     g_object_get(settings, "enable-spell-checking", &enabled, NULL);
319
320     g_object_set(settings, "enable-spell-checking", !enabled, NULL);
321 }
322
323 void EditorClient::toggleGrammarChecking()
324 {
325 }
326
327 void EditorClient::handleKeyboardEvent(KeyboardEvent* event)
328 {
329     Frame* frame = core(m_webView)->focusController()->focusedOrMainFrame();
330     if (!frame || !frame->document()->focusedNode())
331         return;
332
333     const PlatformKeyboardEvent* kevent = event->keyEvent();
334     if (!kevent || kevent->type() == PlatformKeyboardEvent::KeyUp)
335         return;
336
337     Node* start = frame->selection()->start().node();
338     if (!start)
339         return;
340
341     // FIXME: Use GtkBindingSet instead of this hard-coded switch
342     // http://bugs.webkit.org/show_bug.cgi?id=15911
343
344     if (start->isContentEditable()) {
345         switch (kevent->windowsVirtualKeyCode()) {
346             case VK_BACK:
347                 frame->editor()->deleteWithDirection(SelectionController::BACKWARD,
348                         kevent->ctrlKey() ? WordGranularity : CharacterGranularity, false, true);
349                 break;
350             case VK_DELETE:
351                 frame->editor()->deleteWithDirection(SelectionController::FORWARD,
352                         kevent->ctrlKey() ? WordGranularity : CharacterGranularity, false, true);
353                 break;
354             case VK_LEFT:
355                 frame->selection()->modify(kevent->shiftKey() ? SelectionController::EXTEND : SelectionController::MOVE,
356                         SelectionController::LEFT,
357                         kevent->ctrlKey() ? WordGranularity : CharacterGranularity,
358                         true);
359                 break;
360             case VK_RIGHT:
361                 frame->selection()->modify(kevent->shiftKey() ? SelectionController::EXTEND : SelectionController::MOVE,
362                         SelectionController::RIGHT,
363                         kevent->ctrlKey() ? WordGranularity : CharacterGranularity,
364                         true);
365                 break;
366             case VK_UP:
367                 frame->selection()->modify(kevent->shiftKey() ? SelectionController::EXTEND : SelectionController::MOVE,
368                         SelectionController::BACKWARD,
369                         kevent->ctrlKey() ? ParagraphGranularity : LineGranularity,
370                         true);
371                 break;
372             case VK_DOWN:
373                 frame->selection()->modify(kevent->shiftKey() ? SelectionController::EXTEND : SelectionController::MOVE,
374                         SelectionController::FORWARD,
375                         kevent->ctrlKey() ? ParagraphGranularity : LineGranularity,
376                         true);
377                 break;
378             case VK_PRIOR:  // PageUp
379                 frame->editor()->command(kevent->shiftKey() ? "MovePageUpAndModifySelection" : "MovePageUp").execute();
380                 break;
381             case VK_NEXT:  // PageDown
382                 frame->editor()->command(kevent->shiftKey() ? "MovePageDownAndModifySelection" : "MovePageDown").execute();
383                 break;
384             case VK_HOME:
385                 if (kevent->ctrlKey() && kevent->shiftKey())
386                     frame->editor()->command("MoveToBeginningOfDocumentAndModifySelection").execute();
387                 else if (kevent->ctrlKey())
388                     frame->editor()->command("MoveToBeginningOfDocument").execute();
389                 else if (kevent->shiftKey())
390                     frame->editor()->command("MoveToBeginningOfLineAndModifySelection").execute();
391                 else
392                     frame->editor()->command("MoveToBeginningOfLine").execute();
393                 break;
394             case VK_END:
395                 if (kevent->ctrlKey() && kevent->shiftKey())
396                     frame->editor()->command("MoveToEndOfDocumentAndModifySelection").execute();
397                 else if (kevent->ctrlKey())
398                     frame->editor()->command("MoveToEndOfDocument").execute();
399                 else if (kevent->shiftKey())
400                     frame->editor()->command("MoveToEndOfLineAndModifySelection").execute();
401                 else
402                     frame->editor()->command("MoveToEndOfLine").execute();
403                 break;
404             case VK_RETURN:
405                 frame->editor()->command("InsertLineBreak").execute();
406                 break;
407             case VK_TAB:
408                 return;
409             default:
410                 if (!kevent->ctrlKey() && !kevent->altKey() && !kevent->text().isEmpty()) {
411                     if (kevent->text().length() == 1) {
412                         UChar ch = kevent->text()[0];
413                         // Don't insert null or control characters as they can result in unexpected behaviour
414                         if (ch < ' ')
415                             break;
416                     }
417                     frame->editor()->insertText(kevent->text(), event);
418                 } else if (kevent->ctrlKey()) {
419                     switch (kevent->windowsVirtualKeyCode()) {
420                         case VK_B:
421                             frame->editor()->command("ToggleBold").execute();
422                             break;
423                         case VK_I:
424                             frame->editor()->command("ToggleItalic").execute();
425                             break;
426                         case VK_Y:
427                             frame->editor()->command("Redo").execute();
428                             break;
429                         case VK_Z:
430                             frame->editor()->command("Undo").execute();
431                             break;
432                         default:
433                             return;
434                     }
435                 } else return;
436         }
437     } else {
438         switch (kevent->windowsVirtualKeyCode()) {
439             case VK_UP:
440                 frame->editor()->command("MoveUp").execute();
441                 break;
442             case VK_DOWN:
443                 frame->editor()->command("MoveDown").execute();
444                 break;
445             case VK_PRIOR:  // PageUp
446                 frame->editor()->command("MovePageUp").execute();
447                 break;
448             case VK_NEXT:  // PageDown
449                 frame->editor()->command("MovePageDown").execute();
450                 break;
451             case VK_HOME:
452                 if (kevent->ctrlKey())
453                     frame->editor()->command("MoveToBeginningOfDocument").execute();
454                 break;
455             case VK_END:
456                 if (kevent->ctrlKey())
457                     frame->editor()->command("MoveToEndOfDocument").execute();
458                 break;
459             default:
460                 return;
461         }
462     }
463     event->setDefaultHandled();
464 }
465
466 void EditorClient::handleInputMethodKeydown(KeyboardEvent* event)
467 {
468     Frame* targetFrame = core(m_webView)->focusController()->focusedOrMainFrame();
469     if (!targetFrame || !targetFrame->editor()->canEdit())
470         return;
471
472     WebKitWebViewPrivate* priv = m_webView->priv;
473     // TODO: Dispatch IE-compatible text input events for IM events.
474     if (gtk_im_context_filter_keypress(priv->imContext, event->keyEvent()->gdkEventKey()))
475         event->setDefaultHandled();
476 }
477
478 EditorClient::EditorClient(WebKitWebView* webView)
479     : m_webView(webView)
480 {
481     WebKitWebViewPrivate* priv = m_webView->priv;
482     g_signal_connect(priv->imContext, "commit", G_CALLBACK(imContextCommitted), this);
483     g_signal_connect(priv->imContext, "preedit-changed", G_CALLBACK(imContextPreeditChanged), this);
484 }
485
486 EditorClient::~EditorClient()
487 {
488     WebKitWebViewPrivate* priv = m_webView->priv;
489     g_signal_handlers_disconnect_by_func(priv->imContext, (gpointer)imContextCommitted, this);
490     g_signal_handlers_disconnect_by_func(priv->imContext, (gpointer)imContextPreeditChanged, this);
491 }
492
493 void EditorClient::textFieldDidBeginEditing(Element*)
494 {
495 }
496
497 void EditorClient::textFieldDidEndEditing(Element*)
498 {
499 }
500
501 void EditorClient::textDidChangeInTextField(Element*)
502 {
503 }
504
505 bool EditorClient::doTextFieldCommandFromEvent(Element*, KeyboardEvent*)
506 {
507     return false;
508 }
509
510 void EditorClient::textWillBeDeletedInTextField(Element*)
511 {
512     notImplemented();
513 }
514
515 void EditorClient::textDidChangeInTextArea(Element*)
516 {
517     notImplemented();
518 }
519
520 void EditorClient::ignoreWordInSpellDocument(const String& text)
521 {
522     GSList* langs = webkit_web_settings_get_spell_languages(m_webView);
523
524     for (; langs; langs = langs->next) {
525         SpellLanguage* lang = static_cast<SpellLanguage*>(langs->data);
526
527         enchant_dict_add_to_session(lang->speller, text.utf8().data(), -1);
528     }
529 }
530
531 void EditorClient::learnWord(const String& text)
532 {
533     GSList* langs = webkit_web_settings_get_spell_languages(m_webView);
534
535     for (; langs; langs = langs->next) {
536         SpellLanguage* lang = static_cast<SpellLanguage*>(langs->data);
537
538         enchant_dict_add_to_personal(lang->speller, text.utf8().data(), -1);
539     }
540 }
541
542 void EditorClient::checkSpellingOfString(const UChar* text, int length, int* misspellingLocation, int* misspellingLength)
543 {
544     gchar* ctext = g_utf16_to_utf8(const_cast<gunichar2*>(text), length, 0, 0, 0);
545     int utflen = g_utf8_strlen(ctext, -1);
546
547     PangoLanguage* language = pango_language_get_default();
548     PangoLogAttr* attrs = g_new(PangoLogAttr, utflen+1);
549
550     // pango_get_log_attrs uses an aditional position at the end of the text.
551     pango_get_log_attrs(ctext, -1, -1, language, attrs, utflen+1);
552
553     for (int i = 0; i < length+1; i++) {
554         // We go through each character until we find an is_word_start,
555         // then we get into an inner loop to find the is_word_end corresponding
556         // to it.
557         if (attrs[i].is_word_start) {
558             int start = i;
559             int end = i;
560             int wordLength;
561             GSList* langs = webkit_web_settings_get_spell_languages(m_webView);
562
563             while (attrs[end].is_word_end < 1)
564                 end++;
565
566             wordLength = end - start;
567             // Set the iterator to be at the current word end, so we don't
568             // check characters twice.
569             i = end;
570
571             for (; langs; langs = langs->next) {
572                 SpellLanguage* lang = static_cast<SpellLanguage*>(langs->data);
573                 gchar* cstart = g_utf8_offset_to_pointer(ctext, start);
574                 gint bytes = static_cast<gint>(g_utf8_offset_to_pointer(ctext, end) - cstart);
575                 gchar* word = g_new0(gchar, bytes+1);
576                 int result;
577
578                 g_utf8_strncpy(word, cstart, end - start);
579
580                 result = enchant_dict_check(lang->speller, word, -1);
581                 if (result) {
582                     *misspellingLocation = start;
583                     *misspellingLength = wordLength;
584                 } else {
585                     // Stop checking, this word is ok in at least one dict.
586                     *misspellingLocation = -1;
587                     *misspellingLength = 0;
588                     break;
589                 }
590             }
591         }
592     }
593 }
594
595 void EditorClient::checkGrammarOfString(const UChar*, int, Vector<GrammarDetail>&, int*, int*)
596 {
597     notImplemented();
598 }
599
600 void EditorClient::updateSpellingUIWithGrammarString(const String&, const GrammarDetail&)
601 {
602     notImplemented();
603 }
604
605 void EditorClient::updateSpellingUIWithMisspelledWord(const String&)
606 {
607     notImplemented();
608 }
609
610 void EditorClient::showSpellingUI(bool)
611 {
612     notImplemented();
613 }
614
615 bool EditorClient::spellingUIIsShowing()
616 {
617     notImplemented();
618     return false;
619 }
620
621 void EditorClient::getGuessesForWord(const String& word, WTF::Vector<String>& guesses)
622 {
623     GSList* langs = webkit_web_settings_get_spell_languages(m_webView);
624     guesses.clear();
625
626     for (; langs; langs = langs->next) {
627         size_t numberOfSuggestions;
628         size_t i;
629
630         SpellLanguage* lang = static_cast<SpellLanguage*>(langs->data);
631         gchar** suggestions = enchant_dict_suggest(lang->speller, word.utf8().data(), -1, &numberOfSuggestions);
632
633         for (i = 0; i < numberOfSuggestions && i < 10; i++)
634             guesses.append(String::fromUTF8(suggestions[i]));
635
636         if (numberOfSuggestions > 0)
637             enchant_dict_free_suggestions(lang->speller, suggestions);
638     }
639 }
640
641 }