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
29static const char *searchEntryFailedStyle = "GtkEntry#searchEntry {background-color: #ff6666;}";
30
31struct _BrowserSearchBar {
32 GtkToolbar parent;
33
34 WebKitWebView *webView;
35 GtkWidget *entry;
36 GtkCssProvider *cssProvider;
37 GtkWidget *prevButton;
38 GtkWidget *nextButton;
39 GtkWidget *optionsMenu;
40 GtkWidget *caseCheckButton;
41 GtkWidget *begginigWordCheckButton;
42 GtkWidget *capitalAsBegginigWordCheckButton;
43};
44
45G_DEFINE_TYPE(BrowserSearchBar, browser_search_bar, GTK_TYPE_TOOLBAR)
46
47static void setFailedStyleForEntry(BrowserSearchBar *searchBar, gboolean failedSearch)
48{
49 gtk_css_provider_load_from_data(searchBar->cssProvider, failedSearch ? searchEntryFailedStyle : "", -1, NULL);
50}
51
52static 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
78static void searchNext(BrowserSearchBar *searchBar)
79{
80 webkit_find_controller_search_next(webkit_web_view_get_find_controller(searchBar->webView));
81}
82
83static void searchPrevious(BrowserSearchBar *searchBar)
84{
85 webkit_find_controller_search_previous(webkit_web_view_get_find_controller(searchBar->webView));
86}
87
88static void searchCloseButtonClickedCallback(BrowserSearchBar *searchBar)
89{
90 browser_search_bar_close(searchBar);
91}
92
93static void searchEntryMenuIconPressedCallback(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
101static 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
107static void searchEntryChangedCallback(GtkEntry *entry, BrowserSearchBar *searchBar)
108{
109 doSearch(searchBar);
110}
111
112static void searchEntryActivatedCallback(BrowserSearchBar *searchBar)
113{
114 searchNext(searchBar);
115}
116
117static void searchPrevButtonClickedCallback(BrowserSearchBar *searchBar)
118{
119 searchPrevious(searchBar);
120}
121
122static void searchNextButtonClickedCallback(BrowserSearchBar *searchBar)
123{
124 searchNext(searchBar);
125}
126
127static void searchMenuCheckButtonToggledCallback(BrowserSearchBar *searchBar)
128{
129 doSearch(searchBar);
130}
131
132static void findControllerFailedToFindTextCallback(BrowserSearchBar *searchBar)
133{
134 setFailedStyleForEntry(searchBar, TRUE);
135}
136
137static void findControllerFoundTextCallback(BrowserSearchBar *searchBar)
138{
139 setFailedStyleForEntry(searchBar, FALSE);
140}
141
142static 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
218static 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
240static void browser_search_bar_class_init(BrowserSearchBarClass *klass)
241{
242 GObjectClass *gObjectClass = G_OBJECT_CLASS(klass);
243
244 gObjectClass->finalize = browserSearchBarFinalize;
245}
246
247GtkWidget *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
261void 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
273void 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
286void 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