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