1 // Copyright (c) 2011 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "chrome/browser/ui/gtk/find_bar_gtk.h"
6
7 #include <gdk/gdkkeysyms.h>
8
9 #include <algorithm>
10 #include <string>
11 #include <vector>
12
13 #include "base/i18n/rtl.h"
14 #include "base/string_number_conversions.h"
15 #include "base/string_util.h"
16 #include "base/utf_string_conversions.h"
17 #include "chrome/browser/profiles/profile.h"
18 #include "chrome/browser/ui/browser.h"
19 #include "chrome/browser/ui/find_bar/find_bar_controller.h"
20 #include "chrome/browser/ui/find_bar/find_bar_state.h"
21 #include "chrome/browser/ui/find_bar/find_notification_details.h"
22 #include "chrome/browser/ui/find_bar/find_tab_helper.h"
23 #include "chrome/browser/ui/gtk/browser_window_gtk.h"
24 #include "chrome/browser/ui/gtk/cairo_cached_surface.h"
25 #include "chrome/browser/ui/gtk/custom_button.h"
26 #include "chrome/browser/ui/gtk/gtk_floating_container.h"
27 #include "chrome/browser/ui/gtk/gtk_theme_service.h"
28 #include "chrome/browser/ui/gtk/gtk_util.h"
29 #include "chrome/browser/ui/gtk/nine_box.h"
30 #include "chrome/browser/ui/gtk/slide_animator_gtk.h"
31 #include "chrome/browser/ui/gtk/tab_contents_container_gtk.h"
32 #include "chrome/browser/ui/gtk/tabs/tab_strip_gtk.h"
33 #include "chrome/browser/ui/gtk/view_id_util.h"
34 #include "chrome/browser/ui/tab_contents/tab_contents_wrapper.h"
35 #include "content/browser/renderer_host/render_view_host.h"
36 #include "content/browser/tab_contents/tab_contents.h"
37 #include "content/common/native_web_keyboard_event.h"
38 #include "content/common/notification_service.h"
39 #include "grit/generated_resources.h"
40 #include "grit/theme_resources.h"
41 #include "ui/base/l10n/l10n_util.h"
42 #include "ui/base/resource/resource_bundle.h"
43
44 namespace {
45
46 // Used as the color of the text in the entry box and the text for the results
47 // label for failure searches.
48 const GdkColor kEntryTextColor = gtk_util::kGdkBlack;
49
50 // Used as the color of the background of the entry box and the background of
51 // the find label for successful searches.
52 const GdkColor kEntryBackgroundColor = gtk_util::kGdkWhite;
53 const GdkColor kFindFailureBackgroundColor = GDK_COLOR_RGB(255, 102, 102);
54 const GdkColor kFindSuccessTextColor = GDK_COLOR_RGB(178, 178, 178);
55
56 // Padding around the container.
57 const int kBarPaddingTopBottom = 4;
58 const int kEntryPaddingLeft = 6;
59 const int kCloseButtonPaddingLeft = 3;
60 const int kBarPaddingRight = 4;
61
62 // The height of the findbar dialog, as dictated by the size of the background
63 // images.
64 const int kFindBarHeight = 32;
65
66 // The width of the text entry field.
67 const int kTextEntryWidth = 220;
68
69 // The size of the "rounded" corners.
70 const int kCornerSize = 3;
71
72 enum FrameType {
73 FRAME_MASK,
74 FRAME_STROKE,
75 };
76
77 // Returns a list of points that either form the outline of the status bubble
78 // (|type| == FRAME_MASK) or form the inner border around the inner edge
79 // (|type| == FRAME_STROKE).
MakeFramePolygonPoints(int width,int height,FrameType type)80 std::vector<GdkPoint> MakeFramePolygonPoints(int width,
81 int height,
82 FrameType type) {
83 using gtk_util::MakeBidiGdkPoint;
84 std::vector<GdkPoint> points;
85
86 bool ltr = !base::i18n::IsRTL();
87 // If we have a stroke, we have to offset some of our points by 1 pixel.
88 // We have to inset by 1 pixel when we draw horizontal lines that are on the
89 // bottom or when we draw vertical lines that are closer to the end (end is
90 // right for ltr).
91 int y_off = (type == FRAME_MASK) ? 0 : -1;
92 // We use this one for LTR.
93 int x_off_l = ltr ? y_off : 0;
94 // We use this one for RTL.
95 int x_off_r = !ltr ? -y_off : 0;
96
97 // Top left corner
98 points.push_back(MakeBidiGdkPoint(x_off_r, 0, width, ltr));
99 points.push_back(MakeBidiGdkPoint(
100 kCornerSize + x_off_r, kCornerSize, width, ltr));
101
102 // Bottom left corner
103 points.push_back(MakeBidiGdkPoint(
104 kCornerSize + x_off_r, height - kCornerSize, width, ltr));
105 points.push_back(MakeBidiGdkPoint(
106 (2 * kCornerSize) + x_off_l, height + y_off,
107 width, ltr));
108
109 // Bottom right corner
110 points.push_back(MakeBidiGdkPoint(
111 width - (2 * kCornerSize) + x_off_r, height + y_off,
112 width, ltr));
113 points.push_back(MakeBidiGdkPoint(
114 width - kCornerSize + x_off_l, height - kCornerSize, width, ltr));
115
116 // Top right corner
117 points.push_back(MakeBidiGdkPoint(
118 width - kCornerSize + x_off_l, kCornerSize, width, ltr));
119 points.push_back(MakeBidiGdkPoint(width + x_off_l, 0, width, ltr));
120
121 return points;
122 }
123
124 // Give the findbar dialog its unique shape using images.
SetDialogShape(GtkWidget * widget)125 void SetDialogShape(GtkWidget* widget) {
126 static NineBox* dialog_shape = NULL;
127 if (!dialog_shape) {
128 dialog_shape = new NineBox(
129 IDR_FIND_DLG_LEFT_BACKGROUND,
130 IDR_FIND_DLG_MIDDLE_BACKGROUND,
131 IDR_FIND_DLG_RIGHT_BACKGROUND,
132 0, 0, 0, 0, 0, 0);
133 dialog_shape->ChangeWhiteToTransparent();
134 }
135
136 dialog_shape->ContourWidget(widget);
137 }
138
139 // Return a ninebox that will paint the border of the findbar dialog. This is
140 // shared across all instances of the findbar. Do not free the returned pointer.
GetDialogBorder()141 const NineBox* GetDialogBorder() {
142 static NineBox* dialog_border = NULL;
143 if (!dialog_border) {
144 dialog_border = new NineBox(
145 IDR_FIND_DIALOG_LEFT,
146 IDR_FIND_DIALOG_MIDDLE,
147 IDR_FIND_DIALOG_RIGHT,
148 0, 0, 0, 0, 0, 0);
149 }
150
151 return dialog_border;
152 }
153
154 // Like gtk_util::CreateGtkBorderBin, but allows control over the alignment and
155 // returns both the event box and the alignment so we can modify it during its
156 // lifetime (i.e. during a theme change).
BuildBorder(GtkWidget * child,bool center,int padding_top,int padding_bottom,int padding_left,int padding_right,GtkWidget ** ebox,GtkWidget ** alignment)157 void BuildBorder(GtkWidget* child,
158 bool center,
159 int padding_top, int padding_bottom, int padding_left,
160 int padding_right,
161 GtkWidget** ebox, GtkWidget** alignment) {
162 *ebox = gtk_event_box_new();
163 if (center)
164 *alignment = gtk_alignment_new(0.5, 0.5, 0.0, 0.0);
165 else
166 *alignment = gtk_alignment_new(0.0, 0.0, 1.0, 1.0);
167 gtk_alignment_set_padding(GTK_ALIGNMENT(*alignment),
168 padding_top, padding_bottom, padding_left,
169 padding_right);
170 gtk_container_add(GTK_CONTAINER(*alignment), child);
171 gtk_container_add(GTK_CONTAINER(*ebox), *alignment);
172 }
173
174 } // namespace
175
FindBarGtk(Browser * browser)176 FindBarGtk::FindBarGtk(Browser* browser)
177 : browser_(browser),
178 window_(static_cast<BrowserWindowGtk*>(browser->window())),
179 theme_service_(GtkThemeService::GetFrom(browser->profile())),
180 container_width_(-1),
181 container_height_(-1),
182 match_label_failure_(false),
183 ignore_changed_signal_(false) {
184 InitWidgets();
185 ViewIDUtil::SetID(text_entry_, VIEW_ID_FIND_IN_PAGE_TEXT_FIELD);
186
187 // Insert the widget into the browser gtk hierarchy.
188 window_->AddFindBar(this);
189
190 // Hook up signals after the widget has been added to the hierarchy so the
191 // widget will be realized.
192 g_signal_connect(text_entry_, "changed",
193 G_CALLBACK(OnChanged), this);
194 g_signal_connect_after(text_entry_, "key-press-event",
195 G_CALLBACK(OnKeyPressEvent), this);
196 g_signal_connect_after(text_entry_, "key-release-event",
197 G_CALLBACK(OnKeyReleaseEvent), this);
198 // When the user tabs to us or clicks on us, save where the focus used to
199 // be.
200 g_signal_connect(text_entry_, "focus",
201 G_CALLBACK(OnFocus), this);
202 gtk_widget_add_events(text_entry_, GDK_BUTTON_PRESS_MASK);
203 g_signal_connect(text_entry_, "button-press-event",
204 G_CALLBACK(OnButtonPress), this);
205 g_signal_connect(text_entry_, "move-cursor", G_CALLBACK(OnMoveCursor), this);
206 g_signal_connect(text_entry_, "activate", G_CALLBACK(OnActivate), this);
207 g_signal_connect(text_entry_, "direction-changed",
208 G_CALLBACK(OnWidgetDirectionChanged), this);
209 g_signal_connect(text_entry_, "focus-in-event",
210 G_CALLBACK(OnFocusIn), this);
211 g_signal_connect(text_entry_, "focus-out-event",
212 G_CALLBACK(OnFocusOut), this);
213 g_signal_connect(container_, "expose-event",
214 G_CALLBACK(OnExpose), this);
215 }
216
~FindBarGtk()217 FindBarGtk::~FindBarGtk() {
218 }
219
InitWidgets()220 void FindBarGtk::InitWidgets() {
221 // The find bar is basically an hbox with a gtkentry (text box) followed by 3
222 // buttons (previous result, next result, close). We wrap the hbox in a gtk
223 // alignment and a gtk event box to get the padding and light blue
224 // background. We put that event box in a fixed in order to control its
225 // lateral position. We put that fixed in a SlideAnimatorGtk in order to get
226 // the slide effect.
227 GtkWidget* hbox = gtk_hbox_new(false, 0);
228 container_ = gtk_util::CreateGtkBorderBin(hbox, NULL,
229 kBarPaddingTopBottom, kBarPaddingTopBottom,
230 kEntryPaddingLeft, kBarPaddingRight);
231 ViewIDUtil::SetID(container_, VIEW_ID_FIND_IN_PAGE);
232 gtk_widget_set_app_paintable(container_, TRUE);
233
234 slide_widget_.reset(new SlideAnimatorGtk(container_,
235 SlideAnimatorGtk::DOWN,
236 0, false, false, NULL));
237
238 close_button_.reset(CustomDrawButton::CloseButton(theme_service_));
239 gtk_util::CenterWidgetInHBox(hbox, close_button_->widget(), true,
240 kCloseButtonPaddingLeft);
241 g_signal_connect(close_button_->widget(), "clicked",
242 G_CALLBACK(OnClicked), this);
243 gtk_widget_set_tooltip_text(close_button_->widget(),
244 l10n_util::GetStringUTF8(IDS_FIND_IN_PAGE_CLOSE_TOOLTIP).c_str());
245
246 find_next_button_.reset(new CustomDrawButton(theme_service_,
247 IDR_FINDINPAGE_NEXT, IDR_FINDINPAGE_NEXT_H, IDR_FINDINPAGE_NEXT_H,
248 IDR_FINDINPAGE_NEXT_P, GTK_STOCK_GO_DOWN, GTK_ICON_SIZE_MENU));
249 g_signal_connect(find_next_button_->widget(), "clicked",
250 G_CALLBACK(OnClicked), this);
251 gtk_widget_set_tooltip_text(find_next_button_->widget(),
252 l10n_util::GetStringUTF8(IDS_FIND_IN_PAGE_NEXT_TOOLTIP).c_str());
253 gtk_box_pack_end(GTK_BOX(hbox), find_next_button_->widget(),
254 FALSE, FALSE, 0);
255
256 find_previous_button_.reset(new CustomDrawButton(theme_service_,
257 IDR_FINDINPAGE_PREV, IDR_FINDINPAGE_PREV_H, IDR_FINDINPAGE_PREV_H,
258 IDR_FINDINPAGE_PREV_P, GTK_STOCK_GO_UP, GTK_ICON_SIZE_MENU));
259 g_signal_connect(find_previous_button_->widget(), "clicked",
260 G_CALLBACK(OnClicked), this);
261 gtk_widget_set_tooltip_text(find_previous_button_->widget(),
262 l10n_util::GetStringUTF8(IDS_FIND_IN_PAGE_PREVIOUS_TOOLTIP).c_str());
263 gtk_box_pack_end(GTK_BOX(hbox), find_previous_button_->widget(),
264 FALSE, FALSE, 0);
265
266 // Make a box for the edit and match count widgets. This is fixed size since
267 // we want the widgets inside to resize themselves rather than making the
268 // dialog bigger.
269 GtkWidget* content_hbox = gtk_hbox_new(FALSE, 0);
270 gtk_widget_set_size_request(content_hbox, kTextEntryWidth, -1);
271
272 text_entry_ = gtk_entry_new();
273 gtk_entry_set_has_frame(GTK_ENTRY(text_entry_), FALSE);
274
275 match_count_label_ = gtk_label_new(NULL);
276 // This line adds padding on the sides so that the label has even padding on
277 // all edges.
278 gtk_misc_set_padding(GTK_MISC(match_count_label_), 2, 0);
279 match_count_event_box_ = gtk_event_box_new();
280 GtkWidget* match_count_centerer = gtk_vbox_new(FALSE, 0);
281 gtk_box_pack_start(GTK_BOX(match_count_centerer), match_count_event_box_,
282 TRUE, TRUE, 0);
283 gtk_container_set_border_width(GTK_CONTAINER(match_count_centerer), 1);
284 gtk_container_add(GTK_CONTAINER(match_count_event_box_), match_count_label_);
285
286 gtk_box_pack_end(GTK_BOX(content_hbox), match_count_centerer,
287 FALSE, FALSE, 0);
288 gtk_box_pack_end(GTK_BOX(content_hbox), text_entry_, TRUE, TRUE, 0);
289
290 // This event box is necessary to color in the area above and below the match
291 // count label, and is where we draw the entry background onto in GTK mode.
292 BuildBorder(content_hbox, true, 0, 0, 0, 0,
293 &content_event_box_, &content_alignment_);
294 gtk_widget_set_app_paintable(content_event_box_, TRUE);
295 g_signal_connect(content_event_box_, "expose-event",
296 G_CALLBACK(OnContentEventBoxExpose), this);
297
298 // This alignment isn't centered and is used for spacing in chrome theme
299 // mode. (It's also used in GTK mode for padding because left padding doesn't
300 // equal bottom padding naturally.)
301 BuildBorder(content_event_box_, false, 2, 2, 2, 0,
302 &border_bin_, &border_bin_alignment_);
303 gtk_util::CenterWidgetInHBox(hbox, border_bin_, true, 0);
304
305 theme_service_->InitThemesFor(this);
306 registrar_.Add(this, NotificationType::BROWSER_THEME_CHANGED,
307 NotificationService::AllSources());
308
309 g_signal_connect(widget(), "parent-set", G_CALLBACK(OnParentSet), this);
310
311 // We take care to avoid showing the slide animator widget.
312 gtk_widget_show_all(container_);
313 gtk_widget_show(widget());
314 }
315
GetFindBarController() const316 FindBarController* FindBarGtk::GetFindBarController() const {
317 return find_bar_controller_;
318 }
319
SetFindBarController(FindBarController * find_bar_controller)320 void FindBarGtk::SetFindBarController(FindBarController* find_bar_controller) {
321 find_bar_controller_ = find_bar_controller;
322 }
323
Show(bool animate)324 void FindBarGtk::Show(bool animate) {
325 if (animate) {
326 slide_widget_->Open();
327 selection_rect_ = gfx::Rect();
328 Reposition();
329 if (container_->window)
330 gdk_window_raise(container_->window);
331 } else {
332 slide_widget_->OpenWithoutAnimation();
333 }
334 }
335
Hide(bool animate)336 void FindBarGtk::Hide(bool animate) {
337 if (animate)
338 slide_widget_->Close();
339 else
340 slide_widget_->CloseWithoutAnimation();
341 }
342
SetFocusAndSelection()343 void FindBarGtk::SetFocusAndSelection() {
344 StoreOutsideFocus();
345 gtk_widget_grab_focus(text_entry_);
346 // Select all the text.
347 gtk_entry_select_region(GTK_ENTRY(text_entry_), 0, -1);
348 }
349
ClearResults(const FindNotificationDetails & results)350 void FindBarGtk::ClearResults(const FindNotificationDetails& results) {
351 UpdateUIForFindResult(results, string16());
352 }
353
StopAnimation()354 void FindBarGtk::StopAnimation() {
355 slide_widget_->End();
356 }
357
MoveWindowIfNecessary(const gfx::Rect & selection_rect,bool no_redraw)358 void FindBarGtk::MoveWindowIfNecessary(const gfx::Rect& selection_rect,
359 bool no_redraw) {
360 // Not moving the window on demand, so do nothing.
361 }
362
SetFindText(const string16 & find_text)363 void FindBarGtk::SetFindText(const string16& find_text) {
364 std::string find_text_utf8 = UTF16ToUTF8(find_text);
365
366 // Ignore the "changed" signal handler because programatically setting the
367 // text should not fire a "changed" event.
368 ignore_changed_signal_ = true;
369 gtk_entry_set_text(GTK_ENTRY(text_entry_), find_text_utf8.c_str());
370 ignore_changed_signal_ = false;
371 }
372
UpdateUIForFindResult(const FindNotificationDetails & result,const string16 & find_text)373 void FindBarGtk::UpdateUIForFindResult(const FindNotificationDetails& result,
374 const string16& find_text) {
375 if (!result.selection_rect().IsEmpty()) {
376 selection_rect_ = result.selection_rect();
377 int xposition = GetDialogPosition(result.selection_rect()).x();
378 if (xposition != widget()->allocation.x)
379 Reposition();
380 }
381
382 // Once we find a match we no longer want to keep track of what had
383 // focus. EndFindSession will then set the focus to the page content.
384 if (result.number_of_matches() > 0)
385 focus_store_.Store(NULL);
386
387 std::string find_text_utf8 = UTF16ToUTF8(find_text);
388 bool have_valid_range =
389 result.number_of_matches() != -1 && result.active_match_ordinal() != -1;
390
391 std::string entry_text(gtk_entry_get_text(GTK_ENTRY(text_entry_)));
392 if (entry_text != find_text_utf8) {
393 SetFindText(find_text);
394 gtk_entry_select_region(GTK_ENTRY(text_entry_), 0, -1);
395 }
396
397 if (!find_text.empty() && have_valid_range) {
398 gtk_label_set_text(GTK_LABEL(match_count_label_),
399 l10n_util::GetStringFUTF8(IDS_FIND_IN_PAGE_COUNT,
400 base::IntToString16(result.active_match_ordinal()),
401 base::IntToString16(result.number_of_matches())).c_str());
402 UpdateMatchLabelAppearance(result.number_of_matches() == 0 &&
403 result.final_update());
404 } else {
405 // If there was no text entered, we don't show anything in the result count
406 // area.
407 gtk_label_set_text(GTK_LABEL(match_count_label_), "");
408 UpdateMatchLabelAppearance(false);
409 }
410 }
411
AudibleAlert()412 void FindBarGtk::AudibleAlert() {
413 // This call causes a lot of weird bugs, especially when using the custom
414 // frame. TODO(estade): if people complain, re-enable it. See
415 // http://crbug.com/27635 and others.
416 //
417 // gtk_widget_error_bell(widget());
418 }
419
GetDialogPosition(gfx::Rect avoid_overlapping_rect)420 gfx::Rect FindBarGtk::GetDialogPosition(gfx::Rect avoid_overlapping_rect) {
421 bool ltr = !base::i18n::IsRTL();
422 // 15 is the size of the scrollbar, copied from ScrollbarThemeChromium.
423 // The height is not used.
424 // At very low browser widths we can wind up with a negative |dialog_bounds|
425 // width, so clamp it to 0.
426 gfx::Rect dialog_bounds = gfx::Rect(ltr ? 0 : 15, 0,
427 std::max(0, widget()->parent->allocation.width - (ltr ? 15 : 0)), 0);
428
429 GtkRequisition req;
430 gtk_widget_size_request(container_, &req);
431 gfx::Size prefsize(req.width, req.height);
432
433 gfx::Rect view_location(
434 ltr ? dialog_bounds.width() - prefsize.width() : dialog_bounds.x(),
435 dialog_bounds.y(), prefsize.width(), prefsize.height());
436 gfx::Rect new_pos = FindBarController::GetLocationForFindbarView(
437 view_location, dialog_bounds, avoid_overlapping_rect);
438
439 return new_pos;
440 }
441
IsFindBarVisible()442 bool FindBarGtk::IsFindBarVisible() {
443 return GTK_WIDGET_VISIBLE(widget());
444 }
445
RestoreSavedFocus()446 void FindBarGtk::RestoreSavedFocus() {
447 // This function sometimes gets called when we don't have focus. We should do
448 // nothing in this case.
449 if (!gtk_widget_is_focus(text_entry_))
450 return;
451
452 if (focus_store_.widget())
453 gtk_widget_grab_focus(focus_store_.widget());
454 else
455 find_bar_controller_->tab_contents()->tab_contents()->Focus();
456 }
457
GetFindBarTesting()458 FindBarTesting* FindBarGtk::GetFindBarTesting() {
459 return this;
460 }
461
Observe(NotificationType type,const NotificationSource & source,const NotificationDetails & details)462 void FindBarGtk::Observe(NotificationType type,
463 const NotificationSource& source,
464 const NotificationDetails& details) {
465 DCHECK_EQ(type.value, NotificationType::BROWSER_THEME_CHANGED);
466
467 // Force reshapings of the find bar window.
468 container_width_ = -1;
469 container_height_ = -1;
470
471 if (theme_service_->UseGtkTheme()) {
472 gtk_widget_modify_cursor(text_entry_, NULL, NULL);
473 gtk_widget_modify_base(text_entry_, GTK_STATE_NORMAL, NULL);
474 gtk_widget_modify_text(text_entry_, GTK_STATE_NORMAL, NULL);
475
476 // Prevent forced font sizes because it causes the jump up and down
477 // character movement (http://crbug.com/22614), and because it will
478 // prevent centering of the text entry.
479 gtk_util::UndoForceFontSize(text_entry_);
480 gtk_util::UndoForceFontSize(match_count_label_);
481
482 gtk_widget_set_size_request(content_event_box_, -1, -1);
483 gtk_widget_modify_bg(content_event_box_, GTK_STATE_NORMAL, NULL);
484
485 // Replicate the normal GtkEntry behaviour by drawing the entry
486 // background. We set the fake alignment to be the frame thickness.
487 GtkStyle* style = gtk_rc_get_style(text_entry_);
488 gint xborder = style->xthickness;
489 gint yborder = style->ythickness;
490 gtk_alignment_set_padding(GTK_ALIGNMENT(content_alignment_),
491 yborder, yborder, xborder, xborder);
492
493 // We leave left padding on the left, even in GTK mode, as it's required
494 // for the left margin to be equivalent to the bottom margin.
495 gtk_alignment_set_padding(GTK_ALIGNMENT(border_bin_alignment_),
496 0, 0, 1, 0);
497
498 // We need this event box to have its own window in GTK mode for doing the
499 // hacky widget rendering.
500 gtk_event_box_set_visible_window(GTK_EVENT_BOX(border_bin_), TRUE);
501 gtk_widget_set_app_paintable(border_bin_, TRUE);
502
503 gtk_misc_set_alignment(GTK_MISC(match_count_label_), 0.5, 0.5);
504 } else {
505 gtk_widget_modify_cursor(
506 text_entry_, >k_util::kGdkBlack, >k_util::kGdkGray);
507 gtk_widget_modify_base(text_entry_, GTK_STATE_NORMAL,
508 &kEntryBackgroundColor);
509 gtk_widget_modify_text(text_entry_, GTK_STATE_NORMAL,
510 &kEntryTextColor);
511
512 // Until we switch to vector graphics, force the font size.
513 gtk_util::ForceFontSizePixels(text_entry_, 13.4); // 13.4px == 10pt @ 96dpi
514 gtk_util::ForceFontSizePixels(match_count_label_, 13.4);
515
516 // Force the text widget height so it lines up with the buttons regardless
517 // of font size.
518 gtk_widget_set_size_request(content_event_box_, -1, 20);
519 gtk_widget_modify_bg(content_event_box_, GTK_STATE_NORMAL,
520 &kEntryBackgroundColor);
521
522 gtk_alignment_set_padding(GTK_ALIGNMENT(content_alignment_),
523 0.0, 0.0, 0.0, 0.0);
524
525 gtk_alignment_set_padding(GTK_ALIGNMENT(border_bin_alignment_),
526 2, 2, 3, 0);
527
528 // We need this event box to be invisible because we're only going to draw
529 // on the background (but we can't take it out of the heiarchy entirely
530 // because we also need it to take up space).
531 gtk_event_box_set_visible_window(GTK_EVENT_BOX(border_bin_), FALSE);
532 gtk_widget_set_app_paintable(border_bin_, FALSE);
533
534 gtk_misc_set_alignment(GTK_MISC(match_count_label_), 0.5, 1.0);
535
536 ResourceBundle& rb = ResourceBundle::GetSharedInstance();
537 close_button_->SetBackground(
538 theme_service_->GetColor(ThemeService::COLOR_TAB_TEXT),
539 rb.GetBitmapNamed(IDR_CLOSE_BAR),
540 rb.GetBitmapNamed(IDR_CLOSE_BAR_MASK));
541 }
542
543 UpdateMatchLabelAppearance(match_label_failure_);
544 }
545
GetFindBarWindowInfo(gfx::Point * position,bool * fully_visible)546 bool FindBarGtk::GetFindBarWindowInfo(gfx::Point* position,
547 bool* fully_visible) {
548 if (position)
549 *position = GetPosition();
550
551 if (fully_visible) {
552 *fully_visible = !slide_widget_->IsAnimating() &&
553 slide_widget_->IsShowing();
554 }
555 return true;
556 }
557
GetFindText()558 string16 FindBarGtk::GetFindText() {
559 std::string contents(gtk_entry_get_text(GTK_ENTRY(text_entry_)));
560 return UTF8ToUTF16(contents);
561 }
562
GetFindSelectedText()563 string16 FindBarGtk::GetFindSelectedText() {
564 gint cursor_pos;
565 gint selection_bound;
566 g_object_get(G_OBJECT(text_entry_), "cursor-position", &cursor_pos,
567 NULL);
568 g_object_get(G_OBJECT(text_entry_), "selection-bound", &selection_bound,
569 NULL);
570 std::string contents(gtk_entry_get_text(GTK_ENTRY(text_entry_)));
571 return UTF8ToUTF16(contents.substr(cursor_pos, selection_bound));
572 }
573
GetMatchCountText()574 string16 FindBarGtk::GetMatchCountText() {
575 std::string contents(gtk_label_get_text(GTK_LABEL(match_count_label_)));
576 return UTF8ToUTF16(contents);
577 }
578
FindEntryTextInContents(bool forward_search)579 void FindBarGtk::FindEntryTextInContents(bool forward_search) {
580 TabContentsWrapper* tab_contents = find_bar_controller_->tab_contents();
581 if (!tab_contents)
582 return;
583 FindTabHelper* find_tab_helper = tab_contents->find_tab_helper();
584
585 std::string new_contents(gtk_entry_get_text(GTK_ENTRY(text_entry_)));
586
587 if (new_contents.length() > 0) {
588 find_tab_helper->StartFinding(UTF8ToUTF16(new_contents), forward_search,
589 false); // Not case sensitive.
590 } else {
591 // The textbox is empty so we reset.
592 find_tab_helper->StopFinding(FindBarController::kClearSelection);
593 UpdateUIForFindResult(find_tab_helper->find_result(), string16());
594
595 // Clearing the text box should also clear the prepopulate state so that
596 // when we close and reopen the Find box it doesn't show the search we
597 // just deleted.
598 FindBarState* find_bar_state = browser_->profile()->GetFindBarState();
599 find_bar_state->set_last_prepopulate_text(string16());
600 }
601 }
602
UpdateMatchLabelAppearance(bool failure)603 void FindBarGtk::UpdateMatchLabelAppearance(bool failure) {
604 match_label_failure_ = failure;
605 bool use_gtk = theme_service_->UseGtkTheme();
606
607 if (use_gtk) {
608 GtkStyle* style = gtk_rc_get_style(text_entry_);
609 GdkColor normal_bg = style->base[GTK_STATE_NORMAL];
610 GdkColor normal_text = gtk_util::AverageColors(
611 style->text[GTK_STATE_NORMAL], style->base[GTK_STATE_NORMAL]);
612
613 gtk_widget_modify_bg(match_count_event_box_, GTK_STATE_NORMAL,
614 failure ? &kFindFailureBackgroundColor :
615 &normal_bg);
616 gtk_widget_modify_fg(match_count_label_, GTK_STATE_NORMAL,
617 failure ? &kEntryTextColor : &normal_text);
618 } else {
619 gtk_widget_modify_bg(match_count_event_box_, GTK_STATE_NORMAL,
620 failure ? &kFindFailureBackgroundColor :
621 &kEntryBackgroundColor);
622 gtk_widget_modify_fg(match_count_label_, GTK_STATE_NORMAL,
623 failure ? &kEntryTextColor : &kFindSuccessTextColor);
624 }
625 }
626
Reposition()627 void FindBarGtk::Reposition() {
628 if (!IsFindBarVisible())
629 return;
630
631 // This will trigger an allocate, which allows us to reposition.
632 if (widget()->parent)
633 gtk_widget_queue_resize(widget()->parent);
634 }
635
StoreOutsideFocus()636 void FindBarGtk::StoreOutsideFocus() {
637 // |text_entry_| is the only widget in the find bar that can be focused,
638 // so it's the only one we have to check.
639 // TODO(estade): when we make the find bar buttons focusable, we'll have
640 // to change this (same above in RestoreSavedFocus).
641 if (!gtk_widget_is_focus(text_entry_))
642 focus_store_.Store(text_entry_);
643 }
644
MaybeForwardKeyEventToRenderer(GdkEventKey * event)645 bool FindBarGtk::MaybeForwardKeyEventToRenderer(GdkEventKey* event) {
646 switch (event->keyval) {
647 case GDK_Down:
648 case GDK_Up:
649 case GDK_Page_Up:
650 case GDK_Page_Down:
651 break;
652 case GDK_Home:
653 case GDK_End:
654 if ((event->state & gtk_accelerator_get_default_mod_mask()) ==
655 GDK_CONTROL_MASK) {
656 break;
657 }
658 // Fall through.
659 default:
660 return false;
661 }
662
663 TabContentsWrapper* contents = find_bar_controller_->tab_contents();
664 if (!contents)
665 return false;
666
667 RenderViewHost* render_view_host = contents->render_view_host();
668
669 // Make sure we don't have a text field element interfering with keyboard
670 // input. Otherwise Up and Down arrow key strokes get eaten. "Nom Nom Nom".
671 render_view_host->ClearFocusedNode();
672
673 NativeWebKeyboardEvent wke(event);
674 render_view_host->ForwardKeyboardEvent(wke);
675 return true;
676 }
677
AdjustTextAlignment()678 void FindBarGtk::AdjustTextAlignment() {
679 PangoDirection content_dir =
680 pango_find_base_dir(gtk_entry_get_text(GTK_ENTRY(text_entry_)), -1);
681
682 GtkTextDirection widget_dir = gtk_widget_get_direction(text_entry_);
683
684 // Use keymap or widget direction if content does not have strong direction.
685 // It matches the behavior of GtkEntry.
686 if (content_dir == PANGO_DIRECTION_NEUTRAL) {
687 if (GTK_WIDGET_HAS_FOCUS(text_entry_)) {
688 content_dir = gdk_keymap_get_direction(
689 gdk_keymap_get_for_display(gtk_widget_get_display(text_entry_)));
690 } else {
691 if (widget_dir == GTK_TEXT_DIR_RTL)
692 content_dir = PANGO_DIRECTION_RTL;
693 else
694 content_dir = PANGO_DIRECTION_LTR;
695 }
696 }
697
698 if ((widget_dir == GTK_TEXT_DIR_RTL && content_dir == PANGO_DIRECTION_LTR) ||
699 (widget_dir == GTK_TEXT_DIR_LTR && content_dir == PANGO_DIRECTION_RTL)) {
700 gtk_entry_set_alignment(GTK_ENTRY(text_entry_), 1.0);
701 } else {
702 gtk_entry_set_alignment(GTK_ENTRY(text_entry_), 0.0);
703 }
704 }
705
GetPosition()706 gfx::Point FindBarGtk::GetPosition() {
707 gfx::Point point;
708
709 GValue value = { 0, };
710 g_value_init(&value, G_TYPE_INT);
711 gtk_container_child_get_property(GTK_CONTAINER(widget()->parent),
712 widget(), "x", &value);
713 point.set_x(g_value_get_int(&value));
714
715 gtk_container_child_get_property(GTK_CONTAINER(widget()->parent),
716 widget(), "y", &value);
717 point.set_y(g_value_get_int(&value));
718
719 g_value_unset(&value);
720
721 return point;
722 }
723
724 // static
OnParentSet(GtkWidget * widget,GtkObject * old_parent,FindBarGtk * find_bar)725 void FindBarGtk::OnParentSet(GtkWidget* widget, GtkObject* old_parent,
726 FindBarGtk* find_bar) {
727 if (!widget->parent)
728 return;
729
730 g_signal_connect(widget->parent, "set-floating-position",
731 G_CALLBACK(OnSetFloatingPosition), find_bar);
732 }
733
734 // static
OnSetFloatingPosition(GtkFloatingContainer * floating_container,GtkAllocation * allocation,FindBarGtk * find_bar)735 void FindBarGtk::OnSetFloatingPosition(
736 GtkFloatingContainer* floating_container,
737 GtkAllocation* allocation,
738 FindBarGtk* find_bar) {
739 GtkWidget* findbar = find_bar->widget();
740
741 int xposition = find_bar->GetDialogPosition(find_bar->selection_rect_).x();
742
743 GValue value = { 0, };
744 g_value_init(&value, G_TYPE_INT);
745 g_value_set_int(&value, xposition);
746 gtk_container_child_set_property(GTK_CONTAINER(floating_container),
747 findbar, "x", &value);
748
749 g_value_set_int(&value, 0);
750 gtk_container_child_set_property(GTK_CONTAINER(floating_container),
751 findbar, "y", &value);
752 g_value_unset(&value);
753 }
754
755 // static
OnChanged(GtkWindow * window,FindBarGtk * find_bar)756 gboolean FindBarGtk::OnChanged(GtkWindow* window, FindBarGtk* find_bar) {
757 find_bar->AdjustTextAlignment();
758
759 if (!find_bar->ignore_changed_signal_)
760 find_bar->FindEntryTextInContents(true);
761
762 return FALSE;
763 }
764
765 // static
OnKeyPressEvent(GtkWidget * widget,GdkEventKey * event,FindBarGtk * find_bar)766 gboolean FindBarGtk::OnKeyPressEvent(GtkWidget* widget, GdkEventKey* event,
767 FindBarGtk* find_bar) {
768 if (find_bar->MaybeForwardKeyEventToRenderer(event)) {
769 return TRUE;
770 } else if (GDK_Escape == event->keyval) {
771 find_bar->find_bar_controller_->EndFindSession(
772 FindBarController::kKeepSelection);
773 return TRUE;
774 } else if (GDK_Return == event->keyval ||
775 GDK_KP_Enter == event->keyval) {
776 if ((event->state & gtk_accelerator_get_default_mod_mask()) ==
777 GDK_CONTROL_MASK) {
778 find_bar->find_bar_controller_->EndFindSession(
779 FindBarController::kActivateSelection);
780 return TRUE;
781 }
782
783 bool forward = (event->state & gtk_accelerator_get_default_mod_mask()) !=
784 GDK_SHIFT_MASK;
785 find_bar->FindEntryTextInContents(forward);
786 return TRUE;
787 }
788 return FALSE;
789 }
790
791 // static
OnKeyReleaseEvent(GtkWidget * widget,GdkEventKey * event,FindBarGtk * find_bar)792 gboolean FindBarGtk::OnKeyReleaseEvent(GtkWidget* widget, GdkEventKey* event,
793 FindBarGtk* find_bar) {
794 return find_bar->MaybeForwardKeyEventToRenderer(event);
795 }
796
797 // static
OnClicked(GtkWidget * button,FindBarGtk * find_bar)798 void FindBarGtk::OnClicked(GtkWidget* button, FindBarGtk* find_bar) {
799 if (button == find_bar->close_button_->widget()) {
800 find_bar->find_bar_controller_->EndFindSession(
801 FindBarController::kKeepSelection);
802 } else if (button == find_bar->find_previous_button_->widget() ||
803 button == find_bar->find_next_button_->widget()) {
804 find_bar->FindEntryTextInContents(
805 button == find_bar->find_next_button_->widget());
806 } else {
807 NOTREACHED();
808 }
809 }
810
811 // static
OnContentEventBoxExpose(GtkWidget * widget,GdkEventExpose * event,FindBarGtk * bar)812 gboolean FindBarGtk::OnContentEventBoxExpose(GtkWidget* widget,
813 GdkEventExpose* event,
814 FindBarGtk* bar) {
815 if (bar->theme_service_->UseGtkTheme()) {
816 // Draw the text entry background around where we input stuff. Note the
817 // decrement to |width|. We do this because some theme engines
818 // (*cough*Clearlooks*cough*) don't do any blending and use thickness to
819 // make sure that widgets never overlap.
820 int padding = gtk_widget_get_style(widget)->xthickness;
821 GdkRectangle rec = {
822 widget->allocation.x,
823 widget->allocation.y,
824 widget->allocation.width - padding,
825 widget->allocation.height
826 };
827
828 gtk_util::DrawTextEntryBackground(bar->text_entry_, widget,
829 &event->area, &rec);
830 }
831
832 return FALSE;
833 }
834
835 // Used to handle custom painting of |container_|.
OnExpose(GtkWidget * widget,GdkEventExpose * e,FindBarGtk * bar)836 gboolean FindBarGtk::OnExpose(GtkWidget* widget, GdkEventExpose* e,
837 FindBarGtk* bar) {
838 GtkRequisition req;
839 gtk_widget_size_request(widget, &req);
840 gtk_widget_set_size_request(bar->widget(), req.width, -1);
841
842 if (bar->theme_service_->UseGtkTheme()) {
843 if (bar->container_width_ != widget->allocation.width ||
844 bar->container_height_ != widget->allocation.height) {
845 std::vector<GdkPoint> mask_points = MakeFramePolygonPoints(
846 widget->allocation.width, widget->allocation.height, FRAME_MASK);
847 GdkRegion* mask_region = gdk_region_polygon(&mask_points[0],
848 mask_points.size(),
849 GDK_EVEN_ODD_RULE);
850 // Reset the shape.
851 gdk_window_shape_combine_region(widget->window, NULL, 0, 0);
852 gdk_window_shape_combine_region(widget->window, mask_region, 0, 0);
853 gdk_region_destroy(mask_region);
854
855 bar->container_width_ = widget->allocation.width;
856 bar->container_height_ = widget->allocation.height;
857 }
858
859 GdkDrawable* drawable = GDK_DRAWABLE(e->window);
860 GdkGC* gc = gdk_gc_new(drawable);
861 gdk_gc_set_clip_rectangle(gc, &e->area);
862 GdkColor color = bar->theme_service_->GetBorderColor();
863 gdk_gc_set_rgb_fg_color(gc, &color);
864
865 // Stroke the frame border.
866 std::vector<GdkPoint> points = MakeFramePolygonPoints(
867 widget->allocation.width, widget->allocation.height, FRAME_STROKE);
868 gdk_draw_lines(drawable, gc, &points[0], points.size());
869
870 g_object_unref(gc);
871 } else {
872 if (bar->container_width_ != widget->allocation.width ||
873 bar->container_height_ != widget->allocation.height) {
874 // Reset the shape.
875 gdk_window_shape_combine_region(widget->window, NULL, 0, 0);
876 SetDialogShape(bar->container_);
877
878 bar->container_width_ = widget->allocation.width;
879 bar->container_height_ = widget->allocation.height;
880 }
881
882 cairo_t* cr = gdk_cairo_create(GDK_DRAWABLE(widget->window));
883 gdk_cairo_rectangle(cr, &e->area);
884 cairo_clip(cr);
885
886 gfx::Point tabstrip_origin =
887 bar->window_->tabstrip()->GetTabStripOriginForWidget(widget);
888
889 gtk_util::DrawThemedToolbarBackground(widget, cr, e, tabstrip_origin,
890 bar->theme_service_);
891
892 // During chrome theme mode, we need to draw the border around content_hbox
893 // now instead of when we render |border_bin_|. We don't use stacked event
894 // boxes to simulate the effect because we need to blend them with this
895 // background.
896 GtkAllocation border_allocation = bar->border_bin_->allocation;
897
898 // Blit the left part of the background image once on the left.
899 CairoCachedSurface* background_left =
900 bar->theme_service_->GetRTLEnabledSurfaceNamed(
901 IDR_FIND_BOX_BACKGROUND_LEFT, widget);
902 background_left->SetSource(cr, border_allocation.x, border_allocation.y);
903 cairo_pattern_set_extend(cairo_get_source(cr), CAIRO_EXTEND_REPEAT);
904 cairo_rectangle(cr, border_allocation.x, border_allocation.y,
905 background_left->Width(), background_left->Height());
906 cairo_fill(cr);
907
908 // Blit the center part of the background image in all the space between.
909 CairoCachedSurface* background = bar->theme_service_->GetSurfaceNamed(
910 IDR_FIND_BOX_BACKGROUND, widget);
911 background->SetSource(cr,
912 border_allocation.x + background_left->Width(),
913 border_allocation.y);
914 cairo_pattern_set_extend(cairo_get_source(cr), CAIRO_EXTEND_REPEAT);
915 cairo_rectangle(cr,
916 border_allocation.x + background_left->Width(),
917 border_allocation.y,
918 border_allocation.width - background_left->Width(),
919 background->Height());
920 cairo_fill(cr);
921
922 cairo_destroy(cr);
923
924 // Draw the border.
925 GetDialogBorder()->RenderToWidget(widget);
926 }
927
928 // Propagate to the container's child.
929 GtkWidget* child = gtk_bin_get_child(GTK_BIN(widget));
930 if (child)
931 gtk_container_propagate_expose(GTK_CONTAINER(widget), child, e);
932 return TRUE;
933 }
934
935 // static
OnFocus(GtkWidget * text_entry,GtkDirectionType focus,FindBarGtk * find_bar)936 gboolean FindBarGtk::OnFocus(GtkWidget* text_entry, GtkDirectionType focus,
937 FindBarGtk* find_bar) {
938 find_bar->StoreOutsideFocus();
939
940 // Continue propagating the event.
941 return FALSE;
942 }
943
944 // static
OnButtonPress(GtkWidget * text_entry,GdkEventButton * e,FindBarGtk * find_bar)945 gboolean FindBarGtk::OnButtonPress(GtkWidget* text_entry, GdkEventButton* e,
946 FindBarGtk* find_bar) {
947 find_bar->StoreOutsideFocus();
948
949 // Continue propagating the event.
950 return FALSE;
951 }
952
953 // static
OnMoveCursor(GtkEntry * entry,GtkMovementStep step,gint count,gboolean selection,FindBarGtk * bar)954 void FindBarGtk::OnMoveCursor(GtkEntry* entry, GtkMovementStep step, gint count,
955 gboolean selection, FindBarGtk* bar) {
956 static guint signal_id = g_signal_lookup("move-cursor", GTK_TYPE_ENTRY);
957
958 GdkEvent* event = gtk_get_current_event();
959 if (event) {
960 if ((event->type == GDK_KEY_PRESS || event->type == GDK_KEY_RELEASE) &&
961 bar->MaybeForwardKeyEventToRenderer(&(event->key))) {
962 g_signal_stop_emission(entry, signal_id, 0);
963 }
964
965 gdk_event_free(event);
966 }
967 }
968
969 // static
OnActivate(GtkEntry * entry,FindBarGtk * bar)970 void FindBarGtk::OnActivate(GtkEntry* entry, FindBarGtk* bar) {
971 bar->FindEntryTextInContents(true);
972 }
973
974 // static
OnFocusIn(GtkWidget * entry,GdkEventFocus * event,FindBarGtk * find_bar)975 gboolean FindBarGtk::OnFocusIn(GtkWidget* entry, GdkEventFocus* event,
976 FindBarGtk* find_bar) {
977 g_signal_connect(
978 gdk_keymap_get_for_display(gtk_widget_get_display(entry)),
979 "direction-changed",
980 G_CALLBACK(&OnKeymapDirectionChanged), find_bar);
981
982 find_bar->AdjustTextAlignment();
983
984 return FALSE; // Continue propagation.
985 }
986
987 // static
OnFocusOut(GtkWidget * entry,GdkEventFocus * event,FindBarGtk * find_bar)988 gboolean FindBarGtk::OnFocusOut(GtkWidget* entry, GdkEventFocus* event,
989 FindBarGtk* find_bar) {
990 g_signal_handlers_disconnect_by_func(
991 gdk_keymap_get_for_display(gtk_widget_get_display(entry)),
992 reinterpret_cast<gpointer>(&OnKeymapDirectionChanged), find_bar);
993
994 return FALSE; // Continue propagation.
995 }
996