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
44namespace {
45
46// Used as the color of the text in the entry box and the text for the results
47// label for failure searches.
48const 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.
52const GdkColor kEntryBackgroundColor = gtk_util::kGdkWhite;
53const GdkColor kFindFailureBackgroundColor = GDK_COLOR_RGB(255, 102, 102);
54const GdkColor kFindSuccessTextColor = GDK_COLOR_RGB(178, 178, 178);
55
56// Padding around the container.
57const int kBarPaddingTopBottom = 4;
58const int kEntryPaddingLeft = 6;
59const int kCloseButtonPaddingLeft = 3;
60const int kBarPaddingRight = 4;
61
62// The height of the findbar dialog, as dictated by the size of the background
63// images.
64const int kFindBarHeight = 32;
65
66// The width of the text entry field.
67const int kTextEntryWidth = 220;
68
69// The size of the "rounded" corners.
70const int kCornerSize = 3;
71
72enum 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).
80std::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.
125void 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.
141const 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).
157void 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
176FindBarGtk::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
217FindBarGtk::~FindBarGtk() {
218}
219
220void 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
316FindBarController* FindBarGtk::GetFindBarController() const {
317  return find_bar_controller_;
318}
319
320void FindBarGtk::SetFindBarController(FindBarController* find_bar_controller) {
321  find_bar_controller_ = find_bar_controller;
322}
323
324void 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
336void FindBarGtk::Hide(bool animate) {
337  if (animate)
338    slide_widget_->Close();
339  else
340    slide_widget_->CloseWithoutAnimation();
341}
342
343void 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
350void FindBarGtk::ClearResults(const FindNotificationDetails& results) {
351  UpdateUIForFindResult(results, string16());
352}
353
354void FindBarGtk::StopAnimation() {
355  slide_widget_->End();
356}
357
358void FindBarGtk::MoveWindowIfNecessary(const gfx::Rect& selection_rect,
359                                       bool no_redraw) {
360  // Not moving the window on demand, so do nothing.
361}
362
363void 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
373void 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
412void 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
420gfx::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
442bool FindBarGtk::IsFindBarVisible() {
443  return GTK_WIDGET_VISIBLE(widget());
444}
445
446void 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
458FindBarTesting* FindBarGtk::GetFindBarTesting() {
459  return this;
460}
461
462void 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
546bool 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
558string16 FindBarGtk::GetFindText() {
559  std::string contents(gtk_entry_get_text(GTK_ENTRY(text_entry_)));
560  return UTF8ToUTF16(contents);
561}
562
563string16 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
574string16 FindBarGtk::GetMatchCountText() {
575  std::string contents(gtk_label_get_text(GTK_LABEL(match_count_label_)));
576  return UTF8ToUTF16(contents);
577}
578
579void 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
603void 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
627void 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
636void 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
645bool 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
678void 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
706gfx::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
725void 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
735void 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
756gboolean 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
766gboolean 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
792gboolean FindBarGtk::OnKeyReleaseEvent(GtkWidget* widget, GdkEventKey* event,
793                                       FindBarGtk* find_bar) {
794  return find_bar->MaybeForwardKeyEventToRenderer(event);
795}
796
797// static
798void 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
812gboolean 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_|.
836gboolean 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
936gboolean 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
945gboolean 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
954void 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
970void FindBarGtk::OnActivate(GtkEntry* entry, FindBarGtk* bar) {
971  bar->FindEntryTextInContents(true);
972}
973
974// static
975gboolean 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
988gboolean 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