Home | History | Annotate | Download | only in gtk
      1 /*
      2  * Copyright (C) 2010 Apple Inc. All rights reserved.
      3  * Portions Copyright (c) 2010 Motorola Mobility, Inc.  All rights reserved.
      4  * Copyright (C) 2011 Igalia S.L.
      5  *
      6  * Redistribution and use in source and binary forms, with or without
      7  * modification, are permitted provided that the following conditions
      8  * are met:
      9  * 1. Redistributions of source code must retain the above copyright
     10  *    notice, this list of conditions and the following disclaimer.
     11  * 2. Redistributions in binary form must reproduce the above copyright
     12  *    notice, this list of conditions and the following disclaimer in the
     13  *    documentation and/or other materials provided with the distribution.
     14  *
     15  * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
     16  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
     17  * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
     18  * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
     19  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
     20  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
     21  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
     22  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
     23  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
     24  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
     25  * THE POSSIBILITY OF SUCH DAMAGE.
     26  */
     27 
     28 #include "config.h"
     29 #include "WebView.h"
     30 
     31 #include "ChunkedUpdateDrawingAreaProxy.h"
     32 #include "NativeWebKeyboardEvent.h"
     33 #include "NativeWebMouseEvent.h"
     34 #include "NotImplemented.h"
     35 #include "WebContext.h"
     36 #include "WebContextMenuProxy.h"
     37 #include "WebEventFactory.h"
     38 #include "WebViewWidget.h"
     39 #include "WebPageProxy.h"
     40 #include <wtf/text/WTFString.h>
     41 
     42 typedef HashMap<int, const char*> IntConstCharHashMap;
     43 
     44 using namespace WebCore;
     45 
     46 namespace WebKit {
     47 
     48 void WebView::handleFocusInEvent(GtkWidget* widget)
     49 {
     50     if (!(m_isPageActive)) {
     51         m_isPageActive = true;
     52         m_page->viewStateDidChange(WebPageProxy::ViewWindowIsActive);
     53     }
     54 
     55     m_page->viewStateDidChange(WebPageProxy::ViewIsFocused);
     56 }
     57 
     58 void WebView::handleFocusOutEvent(GtkWidget* widget)
     59 {
     60     m_isPageActive = false;
     61     m_page->viewStateDidChange(WebPageProxy::ViewWindowIsActive);
     62 }
     63 
     64 
     65 static void backspaceCallback(GtkWidget* widget, WebView* client)
     66 {
     67     g_signal_stop_emission_by_name(widget, "backspace");
     68     client->addPendingEditorCommand("DeleteBackward");
     69 }
     70 
     71 static void selectAllCallback(GtkWidget* widget, gboolean select, WebView* client)
     72 {
     73     g_signal_stop_emission_by_name(widget, "select-all");
     74     client->addPendingEditorCommand(select ? "SelectAll" : "Unselect");
     75 }
     76 
     77 static void cutClipboardCallback(GtkWidget* widget, WebView* client)
     78 {
     79     g_signal_stop_emission_by_name(widget, "cut-clipboard");
     80     client->addPendingEditorCommand("Cut");
     81 }
     82 
     83 static void copyClipboardCallback(GtkWidget* widget, WebView* client)
     84 {
     85     g_signal_stop_emission_by_name(widget, "copy-clipboard");
     86     client->addPendingEditorCommand("Copy");
     87 }
     88 
     89 static void pasteClipboardCallback(GtkWidget* widget, WebView* client)
     90 {
     91     g_signal_stop_emission_by_name(widget, "paste-clipboard");
     92     client->addPendingEditorCommand("Paste");
     93 }
     94 
     95 static void toggleOverwriteCallback(GtkWidget* widget, EditorClient*)
     96 {
     97     // We don't support toggling the overwrite mode, but the default callback expects
     98     // the GtkTextView to have a layout, so we handle this signal just to stop it.
     99     g_signal_stop_emission_by_name(widget, "toggle-overwrite");
    100 }
    101 
    102 // GTK+ will still send these signals to the web view. So we can safely stop signal
    103 // emission without breaking accessibility.
    104 static void popupMenuCallback(GtkWidget* widget, EditorClient*)
    105 {
    106     g_signal_stop_emission_by_name(widget, "popup-menu");
    107 }
    108 
    109 static void showHelpCallback(GtkWidget* widget, EditorClient*)
    110 {
    111     g_signal_stop_emission_by_name(widget, "show-help");
    112 }
    113 
    114 static const char* const gtkDeleteCommands[][2] = {
    115     { "DeleteBackward",               "DeleteForward"                        }, // Characters
    116     { "DeleteWordBackward",           "DeleteWordForward"                    }, // Word ends
    117     { "DeleteWordBackward",           "DeleteWordForward"                    }, // Words
    118     { "DeleteToBeginningOfLine",      "DeleteToEndOfLine"                    }, // Lines
    119     { "DeleteToBeginningOfLine",      "DeleteToEndOfLine"                    }, // Line ends
    120     { "DeleteToBeginningOfParagraph", "DeleteToEndOfParagraph"               }, // Paragraph ends
    121     { "DeleteToBeginningOfParagraph", "DeleteToEndOfParagraph"               }, // Paragraphs
    122     { 0,                              0                                      } // Whitespace (M-\ in Emacs)
    123 };
    124 
    125 static void deleteFromCursorCallback(GtkWidget* widget, GtkDeleteType deleteType, gint count, WebView* client)
    126 {
    127     g_signal_stop_emission_by_name(widget, "delete-from-cursor");
    128     int direction = count > 0 ? 1 : 0;
    129 
    130     // Ensuring that deleteType <= G_N_ELEMENTS here results in a compiler warning
    131     // that the condition is always true.
    132 
    133     if (deleteType == GTK_DELETE_WORDS) {
    134         if (!direction) {
    135             client->addPendingEditorCommand("MoveWordForward");
    136             client->addPendingEditorCommand("MoveWordBackward");
    137         } else {
    138             client->addPendingEditorCommand("MoveWordBackward");
    139             client->addPendingEditorCommand("MoveWordForward");
    140         }
    141     } else if (deleteType == GTK_DELETE_DISPLAY_LINES) {
    142         if (!direction)
    143             client->addPendingEditorCommand("MoveToBeginningOfLine");
    144         else
    145             client->addPendingEditorCommand("MoveToEndOfLine");
    146     } else if (deleteType == GTK_DELETE_PARAGRAPHS) {
    147         if (!direction)
    148             client->addPendingEditorCommand("MoveToBeginningOfParagraph");
    149         else
    150             client->addPendingEditorCommand("MoveToEndOfParagraph");
    151     }
    152 
    153     const char* rawCommand = gtkDeleteCommands[deleteType][direction];
    154     if (!rawCommand)
    155       return;
    156 
    157     for (int i = 0; i < abs(count); i++)
    158         client->addPendingEditorCommand(rawCommand);
    159 }
    160 
    161 static const char* const gtkMoveCommands[][4] = {
    162     { "MoveBackward",                                   "MoveForward",
    163       "MoveBackwardAndModifySelection",                 "MoveForwardAndModifySelection"             }, // Forward/backward grapheme
    164     { "MoveLeft",                                       "MoveRight",
    165       "MoveBackwardAndModifySelection",                 "MoveForwardAndModifySelection"             }, // Left/right grapheme
    166     { "MoveWordBackward",                               "MoveWordForward",
    167       "MoveWordBackwardAndModifySelection",             "MoveWordForwardAndModifySelection"         }, // Forward/backward word
    168     { "MoveUp",                                         "MoveDown",
    169       "MoveUpAndModifySelection",                       "MoveDownAndModifySelection"                }, // Up/down line
    170     { "MoveToBeginningOfLine",                          "MoveToEndOfLine",
    171       "MoveToBeginningOfLineAndModifySelection",        "MoveToEndOfLineAndModifySelection"         }, // Up/down line ends
    172     { "MoveParagraphForward",                           "MoveParagraphBackward",
    173       "MoveParagraphForwardAndModifySelection",         "MoveParagraphBackwardAndModifySelection"   }, // Up/down paragraphs
    174     { "MoveToBeginningOfParagraph",                     "MoveToEndOfParagraph",
    175       "MoveToBeginningOfParagraphAndModifySelection",   "MoveToEndOfParagraphAndModifySelection"    }, // Up/down paragraph ends.
    176     { "MovePageUp",                                     "MovePageDown",
    177       "MovePageUpAndModifySelection",                   "MovePageDownAndModifySelection"            }, // Up/down page
    178     { "MoveToBeginningOfDocument",                      "MoveToEndOfDocument",
    179       "MoveToBeginningOfDocumentAndModifySelection",    "MoveToEndOfDocumentAndModifySelection"     }, // Begin/end of buffer
    180     { 0,                                                0,
    181       0,                                                0                                           } // Horizontal page movement
    182 };
    183 
    184 static void moveCursorCallback(GtkWidget* widget, GtkMovementStep step, gint count, gboolean extendSelection, WebView* client)
    185 {
    186     g_signal_stop_emission_by_name(widget, "move-cursor");
    187     int direction = count > 0 ? 1 : 0;
    188     if (extendSelection)
    189         direction += 2;
    190 
    191     if (static_cast<unsigned>(step) >= G_N_ELEMENTS(gtkMoveCommands))
    192         return;
    193 
    194     const char* rawCommand = gtkMoveCommands[step][direction];
    195     if (!rawCommand)
    196         return;
    197 
    198     for (int i = 0; i < abs(count); i++)
    199         client->addPendingEditorCommand(rawCommand);
    200 }
    201 
    202 static const unsigned CtrlKey = 1 << 0;
    203 static const unsigned AltKey = 1 << 1;
    204 static const unsigned ShiftKey = 1 << 2;
    205 
    206 struct KeyDownEntry {
    207     unsigned virtualKey;
    208     unsigned modifiers;
    209     const char* name;
    210 };
    211 
    212 struct KeyPressEntry {
    213     unsigned charCode;
    214     unsigned modifiers;
    215     const char* name;
    216 };
    217 
    218 static const KeyDownEntry keyDownEntries[] = {
    219     { 'B',       CtrlKey,            "ToggleBold"                                  },
    220     { 'I',       CtrlKey,            "ToggleItalic"                                },
    221     { VK_ESCAPE, 0,                  "Cancel"                                      },
    222     { VK_OEM_PERIOD, CtrlKey,        "Cancel"                                      },
    223     { VK_TAB,    0,                  "InsertTab"                                   },
    224     { VK_TAB,    ShiftKey,           "InsertBacktab"                               },
    225     { VK_RETURN, 0,                  "InsertNewline"                               },
    226     { VK_RETURN, CtrlKey,            "InsertNewline"                               },
    227     { VK_RETURN, AltKey,             "InsertNewline"                               },
    228     { VK_RETURN, AltKey | ShiftKey,  "InsertNewline"                               },
    229 };
    230 
    231 static const KeyPressEntry keyPressEntries[] = {
    232     { '\t',   0,                  "InsertTab"                                   },
    233     { '\t',   ShiftKey,           "InsertBacktab"                               },
    234     { '\r',   0,                  "InsertNewline"                               },
    235     { '\r',   CtrlKey,            "InsertNewline"                               },
    236     { '\r',   AltKey,             "InsertNewline"                               },
    237     { '\r',   AltKey | ShiftKey,  "InsertNewline"                               },
    238 };
    239 
    240 WebView::WebView(WebContext* context, WebPageGroup* pageGroup)
    241     : m_isPageActive(true)
    242     , m_nativeWidget(gtk_text_view_new())
    243 {
    244     m_page = context->createWebPage(this, pageGroup);
    245 
    246     m_viewWidget = static_cast<GtkWidget*>(g_object_new(WEB_VIEW_TYPE_WIDGET, NULL));
    247     ASSERT(m_viewWidget);
    248 
    249     m_page->initializeWebPage();
    250 
    251     WebViewWidget* webViewWidget = WEB_VIEW_WIDGET(m_viewWidget);
    252     webViewWidgetSetWebViewInstance(webViewWidget, this);
    253 
    254     g_signal_connect(m_nativeWidget.get(), "backspace", G_CALLBACK(backspaceCallback), this);
    255     g_signal_connect(m_nativeWidget.get(), "cut-clipboard", G_CALLBACK(cutClipboardCallback), this);
    256     g_signal_connect(m_nativeWidget.get(), "copy-clipboard", G_CALLBACK(copyClipboardCallback), this);
    257     g_signal_connect(m_nativeWidget.get(), "paste-clipboard", G_CALLBACK(pasteClipboardCallback), this);
    258     g_signal_connect(m_nativeWidget.get(), "select-all", G_CALLBACK(selectAllCallback), this);
    259     g_signal_connect(m_nativeWidget.get(), "move-cursor", G_CALLBACK(moveCursorCallback), this);
    260     g_signal_connect(m_nativeWidget.get(), "delete-from-cursor", G_CALLBACK(deleteFromCursorCallback), this);
    261     g_signal_connect(m_nativeWidget.get(), "toggle-overwrite", G_CALLBACK(toggleOverwriteCallback), this);
    262     g_signal_connect(m_nativeWidget.get(), "popup-menu", G_CALLBACK(popupMenuCallback), this);
    263     g_signal_connect(m_nativeWidget.get(), "show-help", G_CALLBACK(showHelpCallback), this);
    264 }
    265 
    266 WebView::~WebView()
    267 {
    268 }
    269 
    270 GdkWindow* WebView::getWebViewWindow()
    271 {
    272     return gtk_widget_get_window(m_viewWidget);
    273 }
    274 
    275 void WebView::paint(GtkWidget* widget, GdkRectangle rect, cairo_t* cr)
    276 {
    277     m_page->drawingArea()->paint(IntRect(rect), cr);
    278 }
    279 
    280 void WebView::setSize(GtkWidget*, IntSize windowSize)
    281 {
    282     m_page->drawingArea()->setSize(windowSize, IntSize());
    283 }
    284 
    285 void WebView::handleKeyboardEvent(GdkEventKey* event)
    286 {
    287     m_page->handleKeyboardEvent(NativeWebKeyboardEvent(reinterpret_cast<GdkEvent*>(event)));
    288 }
    289 
    290 void WebView::handleMouseEvent(GdkEvent* event, int currentClickCount)
    291 {
    292     m_page->handleMouseEvent(NativeWebMouseEvent(event, currentClickCount));
    293 }
    294 
    295 void WebView::handleWheelEvent(GdkEventScroll* event)
    296 {
    297     m_page->handleWheelEvent(WebEventFactory::createWebWheelEvent(event));
    298 }
    299 
    300 void WebView::getEditorCommandsForKeyEvent(const NativeWebKeyboardEvent& event, Vector<WTF::String>& commandList)
    301 {
    302     m_pendingEditorCommands.clear();
    303 
    304 #ifdef GTK_API_VERSION_2
    305     gtk_bindings_activate_event(GTK_OBJECT(m_nativeWidget.get()), const_cast<GdkEventKey*>(&event.nativeEvent()->key));
    306 #else
    307     gtk_bindings_activate_event(G_OBJECT(m_nativeWidget.get()), const_cast<GdkEventKey*>(&event.nativeEvent()->key));
    308 #endif
    309 
    310     if (m_pendingEditorCommands.isEmpty()) {
    311         commandList.append(m_pendingEditorCommands);
    312         return;
    313     }
    314 
    315     DEFINE_STATIC_LOCAL(IntConstCharHashMap, keyDownCommandsMap, ());
    316     DEFINE_STATIC_LOCAL(IntConstCharHashMap, keyPressCommandsMap, ());
    317 
    318     if (keyDownCommandsMap.isEmpty()) {
    319         for (unsigned i = 0; i < G_N_ELEMENTS(keyDownEntries); i++)
    320             keyDownCommandsMap.set(keyDownEntries[i].modifiers << 16 | keyDownEntries[i].virtualKey, keyDownEntries[i].name);
    321 
    322         for (unsigned i = 0; i < G_N_ELEMENTS(keyPressEntries); i++)
    323             keyPressCommandsMap.set(keyPressEntries[i].modifiers << 16 | keyPressEntries[i].charCode, keyPressEntries[i].name);
    324     }
    325 
    326     unsigned modifiers = 0;
    327     if (event.shiftKey())
    328         modifiers |= ShiftKey;
    329     if (event.altKey())
    330         modifiers |= AltKey;
    331     if (event.controlKey())
    332         modifiers |= CtrlKey;
    333 
    334     // For keypress events, we want charCode(), but keyCode() does that.
    335     int mapKey = modifiers << 16 | event.nativeVirtualKeyCode();
    336     if (mapKey) {
    337         HashMap<int, const char*>* commandMap = event.type() == WebEvent::KeyDown ?
    338             &keyDownCommandsMap : &keyPressCommandsMap;
    339         if (const char* commandString = commandMap->get(mapKey))
    340             m_pendingEditorCommands.append(commandString);
    341     }
    342 
    343     commandList.append(m_pendingEditorCommands);
    344 }
    345 
    346 bool WebView::isActive()
    347 {
    348     return m_isPageActive;
    349 }
    350 
    351 void WebView::close()
    352 {
    353     m_page->close();
    354 }
    355 
    356 // PageClient's pure virtual functions
    357 PassOwnPtr<DrawingAreaProxy> WebView::createDrawingAreaProxy()
    358 {
    359     return ChunkedUpdateDrawingAreaProxy::create(this, m_page.get());
    360 }
    361 
    362 void WebView::setViewNeedsDisplay(const WebCore::IntRect&)
    363 {
    364     notImplemented();
    365 }
    366 
    367 void WebView::displayView()
    368 {
    369     notImplemented();
    370 }
    371 
    372 void WebView::scrollView(const WebCore::IntRect& scrollRect, const WebCore::IntSize& scrollOffset)
    373 {
    374     notImplemented();
    375 }
    376 
    377 WebCore::IntSize WebView::viewSize()
    378 {
    379     GtkAllocation allocation;
    380     gtk_widget_get_allocation(m_viewWidget, &allocation);
    381     return IntSize(allocation.width, allocation.height);
    382 }
    383 
    384 bool WebView::isViewWindowActive()
    385 {
    386     notImplemented();
    387     return true;
    388 }
    389 
    390 bool WebView::isViewFocused()
    391 {
    392     notImplemented();
    393     return true;
    394 }
    395 
    396 bool WebView::isViewVisible()
    397 {
    398     notImplemented();
    399     return true;
    400 }
    401 
    402 bool WebView::isViewInWindow()
    403 {
    404     notImplemented();
    405     return true;
    406 }
    407 
    408 void WebView::WebView::processDidCrash()
    409 {
    410     notImplemented();
    411 }
    412 
    413 void WebView::didRelaunchProcess()
    414 {
    415     notImplemented();
    416 }
    417 
    418 void WebView::takeFocus(bool)
    419 {
    420     notImplemented();
    421 }
    422 
    423 void WebView::toolTipChanged(const String&, const String&)
    424 {
    425     notImplemented();
    426 }
    427 
    428 void WebView::setCursor(const Cursor& cursor)
    429 {
    430     // [GTK] Widget::setCursor() gets called frequently
    431     // http://bugs.webkit.org/show_bug.cgi?id=16388
    432     // Setting the cursor may be an expensive operation in some backends,
    433     // so don't re-set the cursor if it's already set to the target value.
    434     GdkWindow* window = gtk_widget_get_window(m_viewWidget);
    435     GdkCursor* currentCursor = gdk_window_get_cursor(window);
    436     GdkCursor* newCursor = cursor.platformCursor().get();
    437     if (currentCursor != newCursor)
    438         gdk_window_set_cursor(window, newCursor);
    439 }
    440 
    441 void WebView::setViewportArguments(const WebCore::ViewportArguments&)
    442 {
    443     notImplemented();
    444 }
    445 
    446 void WebView::registerEditCommand(PassRefPtr<WebEditCommandProxy>, WebPageProxy::UndoOrRedo)
    447 {
    448     notImplemented();
    449 }
    450 
    451 void WebView::clearAllEditCommands()
    452 {
    453     notImplemented();
    454 }
    455 
    456 bool WebView::canUndoRedo(WebPageProxy::UndoOrRedo)
    457 {
    458     notImplemented();
    459     return false;
    460 }
    461 
    462 void WebView::executeUndoRedo(WebPageProxy::UndoOrRedo)
    463 {
    464     notImplemented();
    465 }
    466 
    467 FloatRect WebView::convertToDeviceSpace(const FloatRect& viewRect)
    468 {
    469     notImplemented();
    470     return viewRect;
    471 }
    472 
    473 FloatRect WebView::convertToUserSpace(const FloatRect& viewRect)
    474 {
    475     notImplemented();
    476     return viewRect;
    477 }
    478 
    479 IntRect WebView::windowToScreen(const IntRect& rect)
    480 {
    481     notImplemented();
    482     return IntRect();
    483 }
    484 
    485 void WebView::doneWithKeyEvent(const NativeWebKeyboardEvent&, bool wasEventHandled)
    486 {
    487     notImplemented();
    488 }
    489 
    490 void WebView::didNotHandleKeyEvent(const NativeWebKeyboardEvent& event)
    491 {
    492     notImplemented();
    493 }
    494 
    495 PassRefPtr<WebPopupMenuProxy> WebView::createPopupMenuProxy(WebPageProxy*)
    496 {
    497     notImplemented();
    498     return 0;
    499 }
    500 
    501 PassRefPtr<WebContextMenuProxy> WebView::createContextMenuProxy(WebPageProxy*)
    502 {
    503     notImplemented();
    504     return 0;
    505 }
    506 
    507 void WebView::setFindIndicator(PassRefPtr<FindIndicator>, bool fadeOut)
    508 {
    509     notImplemented();
    510 }
    511 
    512 #if USE(ACCELERATED_COMPOSITING)
    513 void WebView::pageDidEnterAcceleratedCompositing()
    514 {
    515     notImplemented();
    516 }
    517 
    518 void WebView::pageDidLeaveAcceleratedCompositing()
    519 {
    520     notImplemented();
    521 }
    522 #endif // USE(ACCELERATED_COMPOSITING)
    523 
    524 void WebView::didCommitLoadForMainFrame(bool useCustomRepresentation)
    525 {
    526 }
    527 
    528 void WebView::didFinishLoadingDataForCustomRepresentation(const String& suggestedFilename, const CoreIPC::DataReference&)
    529 {
    530 }
    531 
    532 double WebView::customRepresentationZoomFactor()
    533 {
    534     notImplemented();
    535     return 0;
    536 }
    537 
    538 void WebView::setCustomRepresentationZoomFactor(double)
    539 {
    540     notImplemented();
    541 }
    542 
    543 void WebView::pageClosed()
    544 {
    545     notImplemented();
    546 }
    547 
    548 void WebView::didChangeScrollbarsForMainFrame() const
    549 {
    550 }
    551 
    552 void WebView::flashBackingStoreUpdates(const Vector<IntRect>&)
    553 {
    554     notImplemented();
    555 }
    556 
    557 void WebView::findStringInCustomRepresentation(const String&, FindOptions, unsigned)
    558 {
    559     notImplemented();
    560 }
    561 
    562 void WebView::countStringMatchesInCustomRepresentation(const String&, FindOptions, unsigned)
    563 {
    564     notImplemented();
    565 }
    566 
    567 } // namespace WebKit
    568