• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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_, &gtk_util::kGdkBlack, &gtk_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