| 1 | /* |
| 2 | * Copyright (C) 2013 Igalia S.L. |
| 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 INC. AND ITS CONTRIBUTORS ``AS IS'' |
| 14 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, |
| 15 | * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR |
| 16 | * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS |
| 17 | * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR |
| 18 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF |
| 19 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS |
| 20 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN |
| 21 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) |
| 22 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF |
| 23 | * THE POSSIBILITY OF SUCH DAMAGE. |
| 24 | */ |
| 25 | |
| 26 | #include "cmakeconfig.h" |
| 27 | #include "BrowserSearchBar.h" |
| 28 | |
| 29 | static const char *searchEntryFailedStyle = "GtkEntry#searchEntry {background-color: #ff6666;}" ; |
| 30 | |
| 31 | struct _BrowserSearchBar { |
| 32 | GtkToolbar parent; |
| 33 | |
| 34 | WebKitWebView *webView; |
| 35 | GtkWidget *entry; |
| 36 | GtkCssProvider *cssProvider; |
| 37 | GtkWidget *prevButton; |
| 38 | GtkWidget *nextButton; |
| 39 | GtkWidget *; |
| 40 | GtkWidget *caseCheckButton; |
| 41 | GtkWidget *begginigWordCheckButton; |
| 42 | GtkWidget *capitalAsBegginigWordCheckButton; |
| 43 | }; |
| 44 | |
| 45 | G_DEFINE_TYPE(BrowserSearchBar, browser_search_bar, GTK_TYPE_TOOLBAR) |
| 46 | |
| 47 | static void setFailedStyleForEntry(BrowserSearchBar *searchBar, gboolean failedSearch) |
| 48 | { |
| 49 | gtk_css_provider_load_from_data(searchBar->cssProvider, failedSearch ? searchEntryFailedStyle : "" , -1, NULL); |
| 50 | } |
| 51 | |
| 52 | static void doSearch(BrowserSearchBar *searchBar) |
| 53 | { |
| 54 | GtkEntry *entry = GTK_ENTRY(searchBar->entry); |
| 55 | |
| 56 | if (!gtk_entry_get_text_length(entry)) { |
| 57 | webkit_find_controller_search_finish(webkit_web_view_get_find_controller(searchBar->webView)); |
| 58 | gtk_entry_set_icon_from_stock(entry, GTK_ENTRY_ICON_SECONDARY, NULL); |
| 59 | setFailedStyleForEntry(searchBar, FALSE); |
| 60 | return; |
| 61 | } |
| 62 | |
| 63 | if (!gtk_entry_get_icon_stock(entry, GTK_ENTRY_ICON_SECONDARY)) |
| 64 | gtk_entry_set_icon_from_stock(entry, GTK_ENTRY_ICON_SECONDARY, GTK_STOCK_CLEAR); |
| 65 | |
| 66 | WebKitFindOptions options = WEBKIT_FIND_OPTIONS_WRAP_AROUND; |
| 67 | if (!gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(searchBar->caseCheckButton))) |
| 68 | options |= WEBKIT_FIND_OPTIONS_CASE_INSENSITIVE; |
| 69 | if (gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(searchBar->begginigWordCheckButton))) |
| 70 | options |= WEBKIT_FIND_OPTIONS_AT_WORD_STARTS; |
| 71 | if (gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(searchBar->capitalAsBegginigWordCheckButton))) |
| 72 | options |= WEBKIT_FIND_OPTIONS_TREAT_MEDIAL_CAPITAL_AS_WORD_START; |
| 73 | |
| 74 | const gchar *text = gtk_entry_get_text(entry); |
| 75 | webkit_find_controller_search(webkit_web_view_get_find_controller(searchBar->webView), text, options, G_MAXUINT); |
| 76 | } |
| 77 | |
| 78 | static void searchNext(BrowserSearchBar *searchBar) |
| 79 | { |
| 80 | webkit_find_controller_search_next(webkit_web_view_get_find_controller(searchBar->webView)); |
| 81 | } |
| 82 | |
| 83 | static void searchPrevious(BrowserSearchBar *searchBar) |
| 84 | { |
| 85 | webkit_find_controller_search_previous(webkit_web_view_get_find_controller(searchBar->webView)); |
| 86 | } |
| 87 | |
| 88 | static void searchCloseButtonClickedCallback(BrowserSearchBar *searchBar) |
| 89 | { |
| 90 | browser_search_bar_close(searchBar); |
| 91 | } |
| 92 | |
| 93 | static void (BrowserSearchBar *searchBar, GtkEntryIconPosition iconPosition, GdkEvent *event) |
| 94 | { |
| 95 | if (iconPosition == GTK_ENTRY_ICON_PRIMARY) { |
| 96 | GdkEventButton *eventButton = (GdkEventButton *)event; |
| 97 | gtk_menu_popup(GTK_MENU(searchBar->optionsMenu), NULL, NULL, NULL, NULL, eventButton->button, eventButton->time); |
| 98 | } |
| 99 | } |
| 100 | |
| 101 | static void searchEntryClearIconReleasedCallback(BrowserSearchBar *searchBar, GtkEntryIconPosition iconPosition) |
| 102 | { |
| 103 | if (iconPosition == GTK_ENTRY_ICON_SECONDARY) |
| 104 | gtk_entry_set_text(GTK_ENTRY(searchBar->entry), "" ); |
| 105 | } |
| 106 | |
| 107 | static void searchEntryChangedCallback(GtkEntry *entry, BrowserSearchBar *searchBar) |
| 108 | { |
| 109 | doSearch(searchBar); |
| 110 | } |
| 111 | |
| 112 | static void searchEntryActivatedCallback(BrowserSearchBar *searchBar) |
| 113 | { |
| 114 | searchNext(searchBar); |
| 115 | } |
| 116 | |
| 117 | static void searchPrevButtonClickedCallback(BrowserSearchBar *searchBar) |
| 118 | { |
| 119 | searchPrevious(searchBar); |
| 120 | } |
| 121 | |
| 122 | static void searchNextButtonClickedCallback(BrowserSearchBar *searchBar) |
| 123 | { |
| 124 | searchNext(searchBar); |
| 125 | } |
| 126 | |
| 127 | static void (BrowserSearchBar *searchBar) |
| 128 | { |
| 129 | doSearch(searchBar); |
| 130 | } |
| 131 | |
| 132 | static void findControllerFailedToFindTextCallback(BrowserSearchBar *searchBar) |
| 133 | { |
| 134 | setFailedStyleForEntry(searchBar, TRUE); |
| 135 | } |
| 136 | |
| 137 | static void findControllerFoundTextCallback(BrowserSearchBar *searchBar) |
| 138 | { |
| 139 | setFailedStyleForEntry(searchBar, FALSE); |
| 140 | } |
| 141 | |
| 142 | static void browser_search_bar_init(BrowserSearchBar *searchBar) |
| 143 | { |
| 144 | gtk_widget_set_hexpand(GTK_WIDGET(searchBar), TRUE); |
| 145 | |
| 146 | GtkToolItem *toolItem = gtk_tool_item_new(); |
| 147 | gtk_tool_item_set_expand(toolItem, TRUE); |
| 148 | gtk_toolbar_insert(GTK_TOOLBAR(searchBar), toolItem, 0); |
| 149 | |
| 150 | GtkBox *hBox = GTK_BOX(gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 5)); |
| 151 | gtk_box_set_homogeneous(hBox, TRUE); |
| 152 | gtk_container_add(GTK_CONTAINER(toolItem), GTK_WIDGET(hBox)); |
| 153 | |
| 154 | gtk_box_pack_start(hBox, gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0), TRUE, TRUE, 0); |
| 155 | |
| 156 | searchBar->entry = gtk_entry_new(); |
| 157 | gtk_widget_set_name(searchBar->entry, "searchEntry" ); |
| 158 | gtk_entry_set_placeholder_text(GTK_ENTRY(searchBar->entry), "Search" ); |
| 159 | gtk_entry_set_icon_from_stock(GTK_ENTRY(searchBar->entry), GTK_ENTRY_ICON_PRIMARY, GTK_STOCK_FIND); |
| 160 | gtk_box_pack_start(hBox, searchBar->entry, TRUE, TRUE, 0); |
| 161 | |
| 162 | searchBar->cssProvider = gtk_css_provider_new(); |
| 163 | gtk_style_context_add_provider(gtk_widget_get_style_context(searchBar->entry), GTK_STYLE_PROVIDER(searchBar->cssProvider), GTK_STYLE_PROVIDER_PRIORITY_APPLICATION); |
| 164 | |
| 165 | GtkBox *hBoxButtons = GTK_BOX(gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0)); |
| 166 | gtk_box_pack_start(hBox, GTK_WIDGET(hBoxButtons), TRUE, TRUE, 0); |
| 167 | |
| 168 | searchBar->prevButton = gtk_button_new(); |
| 169 | GtkButton *button = GTK_BUTTON(searchBar->prevButton); |
| 170 | GtkWidget *image = gtk_image_new_from_stock(GTK_STOCK_GO_UP, GTK_ICON_SIZE_SMALL_TOOLBAR); |
| 171 | gtk_button_set_image(button, image); |
| 172 | gtk_button_set_relief(button, GTK_RELIEF_NONE); |
| 173 | gtk_button_set_focus_on_click(button, FALSE); |
| 174 | gtk_box_pack_start(hBoxButtons, searchBar->prevButton, FALSE, FALSE, 0); |
| 175 | |
| 176 | searchBar->nextButton = gtk_button_new(); |
| 177 | button = GTK_BUTTON(searchBar->nextButton); |
| 178 | image = gtk_image_new_from_stock(GTK_STOCK_GO_DOWN, GTK_ICON_SIZE_SMALL_TOOLBAR); |
| 179 | gtk_button_set_image(button, image); |
| 180 | gtk_button_set_relief(button, GTK_RELIEF_NONE); |
| 181 | gtk_button_set_focus_on_click(button, FALSE); |
| 182 | gtk_box_pack_start(hBoxButtons, searchBar->nextButton, FALSE, FALSE, 0); |
| 183 | |
| 184 | GtkWidget *closeButton = gtk_button_new(); |
| 185 | button = GTK_BUTTON(closeButton); |
| 186 | image = gtk_image_new_from_stock(GTK_STOCK_CLOSE, GTK_ICON_SIZE_SMALL_TOOLBAR); |
| 187 | gtk_button_set_image(button, image); |
| 188 | gtk_button_set_relief(button, GTK_RELIEF_NONE); |
| 189 | gtk_button_set_focus_on_click(button, FALSE); |
| 190 | gtk_box_pack_end(hBoxButtons, closeButton, FALSE, FALSE, 0); |
| 191 | |
| 192 | searchBar->optionsMenu = g_object_ref_sink(gtk_menu_new()); |
| 193 | |
| 194 | searchBar->caseCheckButton = gtk_check_menu_item_new_with_mnemonic("Ca_se sensitive" ); |
| 195 | gtk_container_add(GTK_CONTAINER(searchBar->optionsMenu), searchBar->caseCheckButton); |
| 196 | |
| 197 | searchBar->begginigWordCheckButton = gtk_check_menu_item_new_with_mnemonic("Only at the _beginning of words" ); |
| 198 | gtk_container_add(GTK_CONTAINER(searchBar->optionsMenu), searchBar->begginigWordCheckButton); |
| 199 | |
| 200 | searchBar->capitalAsBegginigWordCheckButton = gtk_check_menu_item_new_with_mnemonic("Capital _always as beginning of word" ); |
| 201 | gtk_container_add(GTK_CONTAINER(searchBar->optionsMenu), searchBar->capitalAsBegginigWordCheckButton); |
| 202 | |
| 203 | g_signal_connect_swapped(closeButton, "clicked" , G_CALLBACK(searchCloseButtonClickedCallback), searchBar); |
| 204 | g_signal_connect_swapped(searchBar->entry, "icon-press" , G_CALLBACK(searchEntryMenuIconPressedCallback), searchBar); |
| 205 | g_signal_connect_swapped(searchBar->entry, "icon-release" , G_CALLBACK(searchEntryClearIconReleasedCallback), searchBar); |
| 206 | g_signal_connect_after(searchBar->entry, "changed" , G_CALLBACK(searchEntryChangedCallback), searchBar); |
| 207 | g_signal_connect_swapped(searchBar->entry, "activate" , G_CALLBACK(searchEntryActivatedCallback), searchBar); |
| 208 | g_signal_connect_swapped(searchBar->nextButton, "clicked" , G_CALLBACK(searchNextButtonClickedCallback), searchBar); |
| 209 | g_signal_connect_swapped(searchBar->prevButton, "clicked" , G_CALLBACK(searchPrevButtonClickedCallback), searchBar); |
| 210 | g_signal_connect_swapped(searchBar->caseCheckButton, "toggled" , G_CALLBACK(searchMenuCheckButtonToggledCallback), searchBar); |
| 211 | g_signal_connect_swapped(searchBar->begginigWordCheckButton, "toggled" , G_CALLBACK(searchMenuCheckButtonToggledCallback), searchBar); |
| 212 | g_signal_connect_swapped(searchBar->capitalAsBegginigWordCheckButton, "toggled" , G_CALLBACK(searchMenuCheckButtonToggledCallback), searchBar); |
| 213 | |
| 214 | gtk_widget_show_all(GTK_WIDGET(toolItem)); |
| 215 | gtk_widget_show_all(searchBar->optionsMenu); |
| 216 | } |
| 217 | |
| 218 | static void browserSearchBarFinalize(GObject *gObject) |
| 219 | { |
| 220 | BrowserSearchBar *searchBar = BROWSER_SEARCH_BAR(gObject); |
| 221 | |
| 222 | if (searchBar->webView) { |
| 223 | g_object_unref(searchBar->webView); |
| 224 | searchBar->webView = NULL; |
| 225 | } |
| 226 | |
| 227 | if (searchBar->cssProvider) { |
| 228 | g_object_unref(searchBar->cssProvider); |
| 229 | searchBar->cssProvider = NULL; |
| 230 | } |
| 231 | |
| 232 | if (searchBar->optionsMenu) { |
| 233 | g_object_unref(searchBar->optionsMenu); |
| 234 | searchBar->optionsMenu = NULL; |
| 235 | } |
| 236 | |
| 237 | G_OBJECT_CLASS(browser_search_bar_parent_class)->finalize(gObject); |
| 238 | } |
| 239 | |
| 240 | static void browser_search_bar_class_init(BrowserSearchBarClass *klass) |
| 241 | { |
| 242 | GObjectClass *gObjectClass = G_OBJECT_CLASS(klass); |
| 243 | |
| 244 | gObjectClass->finalize = browserSearchBarFinalize; |
| 245 | } |
| 246 | |
| 247 | GtkWidget *browser_search_bar_new(WebKitWebView *webView) |
| 248 | { |
| 249 | g_return_val_if_fail(WEBKIT_IS_WEB_VIEW(webView), NULL); |
| 250 | |
| 251 | GtkWidget *searchBar = GTK_WIDGET(g_object_new(BROWSER_TYPE_SEARCH_BAR, NULL)); |
| 252 | BROWSER_SEARCH_BAR(searchBar)->webView = g_object_ref(webView); |
| 253 | |
| 254 | WebKitFindController *controller = webkit_web_view_get_find_controller(webView); |
| 255 | g_signal_connect_swapped(controller, "failed-to-find-text" , G_CALLBACK(findControllerFailedToFindTextCallback), searchBar); |
| 256 | g_signal_connect_swapped(controller, "found-text" , G_CALLBACK(findControllerFoundTextCallback), searchBar); |
| 257 | |
| 258 | return searchBar; |
| 259 | } |
| 260 | |
| 261 | void browser_search_bar_add_accelerators(BrowserSearchBar *searchBar, GtkAccelGroup *accelGroup) |
| 262 | { |
| 263 | g_return_if_fail(BROWSER_IS_SEARCH_BAR(searchBar)); |
| 264 | g_return_if_fail(GTK_IS_ACCEL_GROUP(accelGroup)); |
| 265 | |
| 266 | gtk_widget_add_accelerator(searchBar->nextButton, "clicked" , accelGroup, GDK_KEY_F3, 0, GTK_ACCEL_VISIBLE); |
| 267 | gtk_widget_add_accelerator(searchBar->nextButton, "clicked" , accelGroup, GDK_KEY_G, GDK_CONTROL_MASK, GTK_ACCEL_VISIBLE); |
| 268 | |
| 269 | gtk_widget_add_accelerator(searchBar->prevButton, "clicked" , accelGroup, GDK_KEY_F3, GDK_SHIFT_MASK, GTK_ACCEL_VISIBLE); |
| 270 | gtk_widget_add_accelerator(searchBar->prevButton, "clicked" , accelGroup, GDK_KEY_G, GDK_SHIFT_MASK | GDK_CONTROL_MASK, GTK_ACCEL_VISIBLE); |
| 271 | } |
| 272 | |
| 273 | void browser_search_bar_open(BrowserSearchBar *searchBar) |
| 274 | { |
| 275 | g_return_if_fail(BROWSER_IS_SEARCH_BAR(searchBar)); |
| 276 | |
| 277 | GtkEntry *entry = GTK_ENTRY(searchBar->entry); |
| 278 | |
| 279 | gtk_widget_show(GTK_WIDGET(searchBar)); |
| 280 | gtk_widget_grab_focus(GTK_WIDGET(entry)); |
| 281 | gtk_editable_select_region(GTK_EDITABLE(entry), 0, -1); |
| 282 | if (gtk_entry_get_text_length(entry)) |
| 283 | doSearch(searchBar); |
| 284 | } |
| 285 | |
| 286 | void browser_search_bar_close(BrowserSearchBar *searchBar) |
| 287 | { |
| 288 | g_return_if_fail(BROWSER_IS_SEARCH_BAR(searchBar)); |
| 289 | |
| 290 | gtk_widget_hide(GTK_WIDGET(searchBar)); |
| 291 | WebKitFindController *controller = webkit_web_view_get_find_controller(searchBar->webView); |
| 292 | webkit_find_controller_search_finish(controller); |
| 293 | } |
| 294 | |