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 | |