| /* |
| * Copyright (C) 2010 Apple Inc. All rights reserved. |
| * Portions Copyright (c) 2010 Motorola Mobility, Inc. All rights reserved. |
| * Copyright (C) 2011 Igalia S.L. |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions |
| * are met: |
| * 1. Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. |
| * 2. Redistributions in binary form must reproduce the above copyright |
| * notice, this list of conditions and the following disclaimer in the |
| * documentation and/or other materials provided with the distribution. |
| * |
| * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' |
| * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, |
| * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR |
| * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS |
| * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR |
| * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF |
| * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS |
| * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN |
| * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) |
| * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF |
| * THE POSSIBILITY OF SUCH DAMAGE. |
| */ |
| |
| #include "config.h" |
| #include "WebView.h" |
| |
| #include "ChunkedUpdateDrawingAreaProxy.h" |
| #include "NativeWebKeyboardEvent.h" |
| #include "NativeWebMouseEvent.h" |
| #include "NotImplemented.h" |
| #include "WebContext.h" |
| #include "WebContextMenuProxy.h" |
| #include "WebEventFactory.h" |
| #include "WebViewWidget.h" |
| #include "WebPageProxy.h" |
| #include <wtf/text/WTFString.h> |
| |
| typedef HashMap<int, const char*> IntConstCharHashMap; |
| |
| using namespace WebCore; |
| |
| namespace WebKit { |
| |
| void WebView::handleFocusInEvent(GtkWidget* widget) |
| { |
| if (!(m_isPageActive)) { |
| m_isPageActive = true; |
| m_page->viewStateDidChange(WebPageProxy::ViewWindowIsActive); |
| } |
| |
| m_page->viewStateDidChange(WebPageProxy::ViewIsFocused); |
| } |
| |
| void WebView::handleFocusOutEvent(GtkWidget* widget) |
| { |
| m_isPageActive = false; |
| m_page->viewStateDidChange(WebPageProxy::ViewWindowIsActive); |
| } |
| |
| |
| static void backspaceCallback(GtkWidget* widget, WebView* client) |
| { |
| g_signal_stop_emission_by_name(widget, "backspace"); |
| client->addPendingEditorCommand("DeleteBackward"); |
| } |
| |
| static void selectAllCallback(GtkWidget* widget, gboolean select, WebView* client) |
| { |
| g_signal_stop_emission_by_name(widget, "select-all"); |
| client->addPendingEditorCommand(select ? "SelectAll" : "Unselect"); |
| } |
| |
| static void cutClipboardCallback(GtkWidget* widget, WebView* client) |
| { |
| g_signal_stop_emission_by_name(widget, "cut-clipboard"); |
| client->addPendingEditorCommand("Cut"); |
| } |
| |
| static void copyClipboardCallback(GtkWidget* widget, WebView* client) |
| { |
| g_signal_stop_emission_by_name(widget, "copy-clipboard"); |
| client->addPendingEditorCommand("Copy"); |
| } |
| |
| static void pasteClipboardCallback(GtkWidget* widget, WebView* client) |
| { |
| g_signal_stop_emission_by_name(widget, "paste-clipboard"); |
| client->addPendingEditorCommand("Paste"); |
| } |
| |
| static void toggleOverwriteCallback(GtkWidget* widget, EditorClient*) |
| { |
| // We don't support toggling the overwrite mode, but the default callback expects |
| // the GtkTextView to have a layout, so we handle this signal just to stop it. |
| g_signal_stop_emission_by_name(widget, "toggle-overwrite"); |
| } |
| |
| // GTK+ will still send these signals to the web view. So we can safely stop signal |
| // emission without breaking accessibility. |
| static void popupMenuCallback(GtkWidget* widget, EditorClient*) |
| { |
| g_signal_stop_emission_by_name(widget, "popup-menu"); |
| } |
| |
| static void showHelpCallback(GtkWidget* widget, EditorClient*) |
| { |
| g_signal_stop_emission_by_name(widget, "show-help"); |
| } |
| |
| static const char* const gtkDeleteCommands[][2] = { |
| { "DeleteBackward", "DeleteForward" }, // Characters |
| { "DeleteWordBackward", "DeleteWordForward" }, // Word ends |
| { "DeleteWordBackward", "DeleteWordForward" }, // Words |
| { "DeleteToBeginningOfLine", "DeleteToEndOfLine" }, // Lines |
| { "DeleteToBeginningOfLine", "DeleteToEndOfLine" }, // Line ends |
| { "DeleteToBeginningOfParagraph", "DeleteToEndOfParagraph" }, // Paragraph ends |
| { "DeleteToBeginningOfParagraph", "DeleteToEndOfParagraph" }, // Paragraphs |
| { 0, 0 } // Whitespace (M-\ in Emacs) |
| }; |
| |
| static void deleteFromCursorCallback(GtkWidget* widget, GtkDeleteType deleteType, gint count, WebView* client) |
| { |
| g_signal_stop_emission_by_name(widget, "delete-from-cursor"); |
| int direction = count > 0 ? 1 : 0; |
| |
| // Ensuring that deleteType <= G_N_ELEMENTS here results in a compiler warning |
| // that the condition is always true. |
| |
| if (deleteType == GTK_DELETE_WORDS) { |
| if (!direction) { |
| client->addPendingEditorCommand("MoveWordForward"); |
| client->addPendingEditorCommand("MoveWordBackward"); |
| } else { |
| client->addPendingEditorCommand("MoveWordBackward"); |
| client->addPendingEditorCommand("MoveWordForward"); |
| } |
| } else if (deleteType == GTK_DELETE_DISPLAY_LINES) { |
| if (!direction) |
| client->addPendingEditorCommand("MoveToBeginningOfLine"); |
| else |
| client->addPendingEditorCommand("MoveToEndOfLine"); |
| } else if (deleteType == GTK_DELETE_PARAGRAPHS) { |
| if (!direction) |
| client->addPendingEditorCommand("MoveToBeginningOfParagraph"); |
| else |
| client->addPendingEditorCommand("MoveToEndOfParagraph"); |
| } |
| |
| const char* rawCommand = gtkDeleteCommands[deleteType][direction]; |
| if (!rawCommand) |
| return; |
| |
| for (int i = 0; i < abs(count); i++) |
| client->addPendingEditorCommand(rawCommand); |
| } |
| |
| static const char* const gtkMoveCommands[][4] = { |
| { "MoveBackward", "MoveForward", |
| "MoveBackwardAndModifySelection", "MoveForwardAndModifySelection" }, // Forward/backward grapheme |
| { "MoveLeft", "MoveRight", |
| "MoveBackwardAndModifySelection", "MoveForwardAndModifySelection" }, // Left/right grapheme |
| { "MoveWordBackward", "MoveWordForward", |
| "MoveWordBackwardAndModifySelection", "MoveWordForwardAndModifySelection" }, // Forward/backward word |
| { "MoveUp", "MoveDown", |
| "MoveUpAndModifySelection", "MoveDownAndModifySelection" }, // Up/down line |
| { "MoveToBeginningOfLine", "MoveToEndOfLine", |
| "MoveToBeginningOfLineAndModifySelection", "MoveToEndOfLineAndModifySelection" }, // Up/down line ends |
| { "MoveParagraphForward", "MoveParagraphBackward", |
| "MoveParagraphForwardAndModifySelection", "MoveParagraphBackwardAndModifySelection" }, // Up/down paragraphs |
| { "MoveToBeginningOfParagraph", "MoveToEndOfParagraph", |
| "MoveToBeginningOfParagraphAndModifySelection", "MoveToEndOfParagraphAndModifySelection" }, // Up/down paragraph ends. |
| { "MovePageUp", "MovePageDown", |
| "MovePageUpAndModifySelection", "MovePageDownAndModifySelection" }, // Up/down page |
| { "MoveToBeginningOfDocument", "MoveToEndOfDocument", |
| "MoveToBeginningOfDocumentAndModifySelection", "MoveToEndOfDocumentAndModifySelection" }, // Begin/end of buffer |
| { 0, 0, |
| 0, 0 } // Horizontal page movement |
| }; |
| |
| static void moveCursorCallback(GtkWidget* widget, GtkMovementStep step, gint count, gboolean extendSelection, WebView* client) |
| { |
| g_signal_stop_emission_by_name(widget, "move-cursor"); |
| int direction = count > 0 ? 1 : 0; |
| if (extendSelection) |
| direction += 2; |
| |
| if (static_cast<unsigned>(step) >= G_N_ELEMENTS(gtkMoveCommands)) |
| return; |
| |
| const char* rawCommand = gtkMoveCommands[step][direction]; |
| if (!rawCommand) |
| return; |
| |
| for (int i = 0; i < abs(count); i++) |
| client->addPendingEditorCommand(rawCommand); |
| } |
| |
| static const unsigned CtrlKey = 1 << 0; |
| static const unsigned AltKey = 1 << 1; |
| static const unsigned ShiftKey = 1 << 2; |
| |
| struct KeyDownEntry { |
| unsigned virtualKey; |
| unsigned modifiers; |
| const char* name; |
| }; |
| |
| struct KeyPressEntry { |
| unsigned charCode; |
| unsigned modifiers; |
| const char* name; |
| }; |
| |
| static const KeyDownEntry keyDownEntries[] = { |
| { 'B', CtrlKey, "ToggleBold" }, |
| { 'I', CtrlKey, "ToggleItalic" }, |
| { VK_ESCAPE, 0, "Cancel" }, |
| { VK_OEM_PERIOD, CtrlKey, "Cancel" }, |
| { VK_TAB, 0, "InsertTab" }, |
| { VK_TAB, ShiftKey, "InsertBacktab" }, |
| { VK_RETURN, 0, "InsertNewline" }, |
| { VK_RETURN, CtrlKey, "InsertNewline" }, |
| { VK_RETURN, AltKey, "InsertNewline" }, |
| { VK_RETURN, AltKey | ShiftKey, "InsertNewline" }, |
| }; |
| |
| static const KeyPressEntry keyPressEntries[] = { |
| { '\t', 0, "InsertTab" }, |
| { '\t', ShiftKey, "InsertBacktab" }, |
| { '\r', 0, "InsertNewline" }, |
| { '\r', CtrlKey, "InsertNewline" }, |
| { '\r', AltKey, "InsertNewline" }, |
| { '\r', AltKey | ShiftKey, "InsertNewline" }, |
| }; |
| |
| WebView::WebView(WebContext* context, WebPageGroup* pageGroup) |
| : m_isPageActive(true) |
| , m_nativeWidget(gtk_text_view_new()) |
| { |
| m_page = context->createWebPage(this, pageGroup); |
| |
| m_viewWidget = static_cast<GtkWidget*>(g_object_new(WEB_VIEW_TYPE_WIDGET, NULL)); |
| ASSERT(m_viewWidget); |
| |
| m_page->initializeWebPage(); |
| |
| WebViewWidget* webViewWidget = WEB_VIEW_WIDGET(m_viewWidget); |
| webViewWidgetSetWebViewInstance(webViewWidget, this); |
| |
| g_signal_connect(m_nativeWidget.get(), "backspace", G_CALLBACK(backspaceCallback), this); |
| g_signal_connect(m_nativeWidget.get(), "cut-clipboard", G_CALLBACK(cutClipboardCallback), this); |
| g_signal_connect(m_nativeWidget.get(), "copy-clipboard", G_CALLBACK(copyClipboardCallback), this); |
| g_signal_connect(m_nativeWidget.get(), "paste-clipboard", G_CALLBACK(pasteClipboardCallback), this); |
| g_signal_connect(m_nativeWidget.get(), "select-all", G_CALLBACK(selectAllCallback), this); |
| g_signal_connect(m_nativeWidget.get(), "move-cursor", G_CALLBACK(moveCursorCallback), this); |
| g_signal_connect(m_nativeWidget.get(), "delete-from-cursor", G_CALLBACK(deleteFromCursorCallback), this); |
| g_signal_connect(m_nativeWidget.get(), "toggle-overwrite", G_CALLBACK(toggleOverwriteCallback), this); |
| g_signal_connect(m_nativeWidget.get(), "popup-menu", G_CALLBACK(popupMenuCallback), this); |
| g_signal_connect(m_nativeWidget.get(), "show-help", G_CALLBACK(showHelpCallback), this); |
| } |
| |
| WebView::~WebView() |
| { |
| } |
| |
| GdkWindow* WebView::getWebViewWindow() |
| { |
| return gtk_widget_get_window(m_viewWidget); |
| } |
| |
| void WebView::paint(GtkWidget* widget, GdkRectangle rect, cairo_t* cr) |
| { |
| m_page->drawingArea()->paint(IntRect(rect), cr); |
| } |
| |
| void WebView::setSize(GtkWidget*, IntSize windowSize) |
| { |
| m_page->drawingArea()->setSize(windowSize, IntSize()); |
| } |
| |
| void WebView::handleKeyboardEvent(GdkEventKey* event) |
| { |
| m_page->handleKeyboardEvent(NativeWebKeyboardEvent(reinterpret_cast<GdkEvent*>(event))); |
| } |
| |
| void WebView::handleMouseEvent(GdkEvent* event, int currentClickCount) |
| { |
| m_page->handleMouseEvent(NativeWebMouseEvent(event, currentClickCount)); |
| } |
| |
| void WebView::handleWheelEvent(GdkEventScroll* event) |
| { |
| m_page->handleWheelEvent(WebEventFactory::createWebWheelEvent(event)); |
| } |
| |
| void WebView::getEditorCommandsForKeyEvent(const NativeWebKeyboardEvent& event, Vector<WTF::String>& commandList) |
| { |
| m_pendingEditorCommands.clear(); |
| |
| #ifdef GTK_API_VERSION_2 |
| gtk_bindings_activate_event(GTK_OBJECT(m_nativeWidget.get()), const_cast<GdkEventKey*>(&event.nativeEvent()->key)); |
| #else |
| gtk_bindings_activate_event(G_OBJECT(m_nativeWidget.get()), const_cast<GdkEventKey*>(&event.nativeEvent()->key)); |
| #endif |
| |
| if (m_pendingEditorCommands.isEmpty()) { |
| commandList.append(m_pendingEditorCommands); |
| return; |
| } |
| |
| DEFINE_STATIC_LOCAL(IntConstCharHashMap, keyDownCommandsMap, ()); |
| DEFINE_STATIC_LOCAL(IntConstCharHashMap, keyPressCommandsMap, ()); |
| |
| if (keyDownCommandsMap.isEmpty()) { |
| for (unsigned i = 0; i < G_N_ELEMENTS(keyDownEntries); i++) |
| keyDownCommandsMap.set(keyDownEntries[i].modifiers << 16 | keyDownEntries[i].virtualKey, keyDownEntries[i].name); |
| |
| for (unsigned i = 0; i < G_N_ELEMENTS(keyPressEntries); i++) |
| keyPressCommandsMap.set(keyPressEntries[i].modifiers << 16 | keyPressEntries[i].charCode, keyPressEntries[i].name); |
| } |
| |
| unsigned modifiers = 0; |
| if (event.shiftKey()) |
| modifiers |= ShiftKey; |
| if (event.altKey()) |
| modifiers |= AltKey; |
| if (event.controlKey()) |
| modifiers |= CtrlKey; |
| |
| // For keypress events, we want charCode(), but keyCode() does that. |
| int mapKey = modifiers << 16 | event.nativeVirtualKeyCode(); |
| if (mapKey) { |
| HashMap<int, const char*>* commandMap = event.type() == WebEvent::KeyDown ? |
| &keyDownCommandsMap : &keyPressCommandsMap; |
| if (const char* commandString = commandMap->get(mapKey)) |
| m_pendingEditorCommands.append(commandString); |
| } |
| |
| commandList.append(m_pendingEditorCommands); |
| } |
| |
| bool WebView::isActive() |
| { |
| return m_isPageActive; |
| } |
| |
| void WebView::close() |
| { |
| m_page->close(); |
| } |
| |
| // PageClient's pure virtual functions |
| PassOwnPtr<DrawingAreaProxy> WebView::createDrawingAreaProxy() |
| { |
| return ChunkedUpdateDrawingAreaProxy::create(this, m_page.get()); |
| } |
| |
| void WebView::setViewNeedsDisplay(const WebCore::IntRect&) |
| { |
| notImplemented(); |
| } |
| |
| void WebView::displayView() |
| { |
| notImplemented(); |
| } |
| |
| void WebView::scrollView(const WebCore::IntRect& scrollRect, const WebCore::IntSize& scrollOffset) |
| { |
| notImplemented(); |
| } |
| |
| WebCore::IntSize WebView::viewSize() |
| { |
| GtkAllocation allocation; |
| gtk_widget_get_allocation(m_viewWidget, &allocation); |
| return IntSize(allocation.width, allocation.height); |
| } |
| |
| bool WebView::isViewWindowActive() |
| { |
| notImplemented(); |
| return true; |
| } |
| |
| bool WebView::isViewFocused() |
| { |
| notImplemented(); |
| return true; |
| } |
| |
| bool WebView::isViewVisible() |
| { |
| notImplemented(); |
| return true; |
| } |
| |
| bool WebView::isViewInWindow() |
| { |
| notImplemented(); |
| return true; |
| } |
| |
| void WebView::WebView::processDidCrash() |
| { |
| notImplemented(); |
| } |
| |
| void WebView::didRelaunchProcess() |
| { |
| notImplemented(); |
| } |
| |
| void WebView::takeFocus(bool) |
| { |
| notImplemented(); |
| } |
| |
| void WebView::toolTipChanged(const String&, const String&) |
| { |
| notImplemented(); |
| } |
| |
| void WebView::setCursor(const Cursor& cursor) |
| { |
| // [GTK] Widget::setCursor() gets called frequently |
| // http://bugs.webkit.org/show_bug.cgi?id=16388 |
| // Setting the cursor may be an expensive operation in some backends, |
| // so don't re-set the cursor if it's already set to the target value. |
| GdkWindow* window = gtk_widget_get_window(m_viewWidget); |
| GdkCursor* currentCursor = gdk_window_get_cursor(window); |
| GdkCursor* newCursor = cursor.platformCursor().get(); |
| if (currentCursor != newCursor) |
| gdk_window_set_cursor(window, newCursor); |
| } |
| |
| void WebView::setViewportArguments(const WebCore::ViewportArguments&) |
| { |
| notImplemented(); |
| } |
| |
| void WebView::registerEditCommand(PassRefPtr<WebEditCommandProxy>, WebPageProxy::UndoOrRedo) |
| { |
| notImplemented(); |
| } |
| |
| void WebView::clearAllEditCommands() |
| { |
| notImplemented(); |
| } |
| |
| bool WebView::canUndoRedo(WebPageProxy::UndoOrRedo) |
| { |
| notImplemented(); |
| return false; |
| } |
| |
| void WebView::executeUndoRedo(WebPageProxy::UndoOrRedo) |
| { |
| notImplemented(); |
| } |
| |
| FloatRect WebView::convertToDeviceSpace(const FloatRect& viewRect) |
| { |
| notImplemented(); |
| return viewRect; |
| } |
| |
| FloatRect WebView::convertToUserSpace(const FloatRect& viewRect) |
| { |
| notImplemented(); |
| return viewRect; |
| } |
| |
| IntRect WebView::windowToScreen(const IntRect& rect) |
| { |
| notImplemented(); |
| return IntRect(); |
| } |
| |
| void WebView::doneWithKeyEvent(const NativeWebKeyboardEvent&, bool wasEventHandled) |
| { |
| notImplemented(); |
| } |
| |
| void WebView::didNotHandleKeyEvent(const NativeWebKeyboardEvent& event) |
| { |
| notImplemented(); |
| } |
| |
| PassRefPtr<WebPopupMenuProxy> WebView::createPopupMenuProxy(WebPageProxy*) |
| { |
| notImplemented(); |
| return 0; |
| } |
| |
| PassRefPtr<WebContextMenuProxy> WebView::createContextMenuProxy(WebPageProxy*) |
| { |
| notImplemented(); |
| return 0; |
| } |
| |
| void WebView::setFindIndicator(PassRefPtr<FindIndicator>, bool fadeOut) |
| { |
| notImplemented(); |
| } |
| |
| #if USE(ACCELERATED_COMPOSITING) |
| void WebView::pageDidEnterAcceleratedCompositing() |
| { |
| notImplemented(); |
| } |
| |
| void WebView::pageDidLeaveAcceleratedCompositing() |
| { |
| notImplemented(); |
| } |
| #endif // USE(ACCELERATED_COMPOSITING) |
| |
| void WebView::didCommitLoadForMainFrame(bool useCustomRepresentation) |
| { |
| } |
| |
| void WebView::didFinishLoadingDataForCustomRepresentation(const String& suggestedFilename, const CoreIPC::DataReference&) |
| { |
| } |
| |
| double WebView::customRepresentationZoomFactor() |
| { |
| notImplemented(); |
| return 0; |
| } |
| |
| void WebView::setCustomRepresentationZoomFactor(double) |
| { |
| notImplemented(); |
| } |
| |
| void WebView::pageClosed() |
| { |
| notImplemented(); |
| } |
| |
| void WebView::didChangeScrollbarsForMainFrame() const |
| { |
| } |
| |
| void WebView::flashBackingStoreUpdates(const Vector<IntRect>&) |
| { |
| notImplemented(); |
| } |
| |
| void WebView::findStringInCustomRepresentation(const String&, FindOptions, unsigned) |
| { |
| notImplemented(); |
| } |
| |
| void WebView::countStringMatchesInCustomRepresentation(const String&, FindOptions, unsigned) |
| { |
| notImplemented(); |
| } |
| |
| } // namespace WebKit |