root/chrome/browser/ui/gtk/find_bar_gtk.cc

/* [<][>][^][v][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. MakeFramePolygonPoints
  2. SetDialogShape
  3. GetDialogBorder
  4. BuildBorder
  5. ignore_changed_signal_
  6. InitWidgets
  7. GetFindBarController
  8. SetFindBarController
  9. Show
  10. Hide
  11. SetFocusAndSelection
  12. ClearResults
  13. StopAnimation
  14. MoveWindowIfNecessary
  15. SetFindTextAndSelectedRange
  16. GetFindText
  17. GetSelectedRange
  18. UpdateUIForFindResult
  19. AudibleAlert
  20. GetDialogPosition
  21. IsFindBarVisible
  22. RestoreSavedFocus
  23. HasGlobalFindPasteboard
  24. UpdateFindBarForChangedWebContents
  25. GetFindBarTesting
  26. Observe
  27. GetFindBarWindowInfo
  28. GetFindSelectedText
  29. GetMatchCountText
  30. GetWidth
  31. FindEntryTextInContents
  32. UpdateMatchLabelAppearance
  33. Reposition
  34. StoreOutsideFocus
  35. MaybeForwardKeyEventToRenderer
  36. AdjustTextAlignment
  37. GetPosition
  38. OnParentSet
  39. OnSetFloatingPosition
  40. OnChanged
  41. OnKeyPressEvent
  42. OnKeyReleaseEvent
  43. OnClicked
  44. OnContentEventBoxExpose
  45. OnExpose
  46. OnFocus
  47. OnButtonPress
  48. OnMoveCursor
  49. OnActivate
  50. OnFocusIn
  51. OnFocusOut

// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "chrome/browser/ui/gtk/find_bar_gtk.h"

#include <gdk/gdkkeysyms.h>

#include <algorithm>
#include <string>
#include <vector>

#include "base/debug/trace_event.h"
#include "base/i18n/rtl.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_util.h"
#include "base/strings/utf_string_conversions.h"
#include "chrome/browser/chrome_notification_types.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/themes/theme_properties.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/find_bar/find_bar_controller.h"
#include "chrome/browser/ui/find_bar/find_bar_state.h"
#include "chrome/browser/ui/find_bar/find_bar_state_factory.h"
#include "chrome/browser/ui/find_bar/find_notification_details.h"
#include "chrome/browser/ui/find_bar/find_tab_helper.h"
#include "chrome/browser/ui/gtk/browser_window_gtk.h"
#include "chrome/browser/ui/gtk/custom_button.h"
#include "chrome/browser/ui/gtk/gtk_theme_service.h"
#include "chrome/browser/ui/gtk/gtk_util.h"
#include "chrome/browser/ui/gtk/nine_box.h"
#include "chrome/browser/ui/gtk/slide_animator_gtk.h"
#include "chrome/browser/ui/gtk/tab_contents_container_gtk.h"
#include "chrome/browser/ui/gtk/tabs/tab_strip_gtk.h"
#include "chrome/browser/ui/gtk/view_id_util.h"
#include "content/public/browser/native_web_keyboard_event.h"
#include "content/public/browser/notification_source.h"
#include "content/public/browser/render_view_host.h"
#include "content/public/browser/web_contents.h"
#include "content/public/browser/web_contents_view.h"
#include "grit/generated_resources.h"
#include "grit/theme_resources.h"
#include "grit/ui_resources.h"
#include "ui/base/gtk/gtk_floating_container.h"
#include "ui/base/gtk/gtk_hig_constants.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/base/resource/resource_bundle.h"
#include "ui/gfx/image/cairo_cached_surface.h"
#include "ui/gfx/image/image.h"

using content::NativeWebKeyboardEvent;

namespace {

// Used as the color of the text in the entry box and the text for the results
// label for failure searches.
const GdkColor& kEntryTextColor = ui::kGdkBlack;

// Used as the color of the background of the entry box and the background of
// the find label for successful searches.
const GdkColor& kEntryBackgroundColor = ui::kGdkWhite;
const GdkColor kFindFailureBackgroundColor = GDK_COLOR_RGB(255, 102, 102);
const GdkColor kFindSuccessTextColor = GDK_COLOR_RGB(178, 178, 178);

// Padding around the container.
const int kBarPaddingTop = 2;
const int kBarPaddingBottom = 3;
const int kEntryPaddingLeft = 6;
const int kCloseButtonPadding = 3;
const int kBarPaddingRight = 4;

// The height of the findbar dialog, as dictated by the size of the background
// images.
const int kFindBarHeight = 32;

// The default width of the findbar dialog. It may get smaller if the window
// is narrow.
const int kFindBarWidth = 303;

// The size of the "rounded" corners.
const int kCornerSize = 3;

enum FrameType {
  FRAME_MASK,
  FRAME_STROKE,
};

// Returns a list of points that either form the outline of the status bubble
// (|type| == FRAME_MASK) or form the inner border around the inner edge
// (|type| == FRAME_STROKE).
std::vector<GdkPoint> MakeFramePolygonPoints(int width,
                                             int height,
                                             FrameType type) {
  using gtk_util::MakeBidiGdkPoint;
  std::vector<GdkPoint> points;

  bool ltr = !base::i18n::IsRTL();
  // If we have a stroke, we have to offset some of our points by 1 pixel.
  // We have to inset by 1 pixel when we draw horizontal lines that are on the
  // bottom or when we draw vertical lines that are closer to the end (end is
  // right for ltr).
  int y_off = (type == FRAME_MASK) ? 0 : -1;
  // We use this one for LTR.
  int x_off_l = ltr ? y_off : 0;
  // We use this one for RTL.
  int x_off_r = !ltr ? -y_off : 0;

  // Top left corner
  points.push_back(MakeBidiGdkPoint(x_off_r, 0, width, ltr));
  points.push_back(MakeBidiGdkPoint(
      kCornerSize + x_off_r, kCornerSize, width, ltr));

  // Bottom left corner
  points.push_back(MakeBidiGdkPoint(
      kCornerSize + x_off_r, height - kCornerSize, width, ltr));
  points.push_back(MakeBidiGdkPoint(
      (2 * kCornerSize) + x_off_l, height + y_off,
      width, ltr));

  // Bottom right corner
  points.push_back(MakeBidiGdkPoint(
      width - (2 * kCornerSize) + x_off_r, height + y_off,
      width, ltr));
  points.push_back(MakeBidiGdkPoint(
      width - kCornerSize + x_off_l, height - kCornerSize, width, ltr));

  // Top right corner
  points.push_back(MakeBidiGdkPoint(
      width - kCornerSize + x_off_l, kCornerSize, width, ltr));
  points.push_back(MakeBidiGdkPoint(width + x_off_l, 0, width, ltr));

  return points;
}

// Give the findbar dialog its unique shape using images.
void SetDialogShape(GtkWidget* widget) {
  static NineBox* dialog_shape = NULL;
  if (!dialog_shape) {
    dialog_shape = new NineBox(
      IDR_FIND_DLG_LEFT_BACKGROUND,
      IDR_FIND_DLG_MIDDLE_BACKGROUND,
      IDR_FIND_DLG_RIGHT_BACKGROUND,
      0, 0, 0, 0, 0, 0);
  }

  dialog_shape->ContourWidget(widget);
}

// Return a ninebox that will paint the border of the findbar dialog. This is
// shared across all instances of the findbar. Do not free the returned pointer.
const NineBox* GetDialogBorder() {
  static NineBox* dialog_border = NULL;
  if (!dialog_border) {
    dialog_border = new NineBox(
      IDR_FIND_DIALOG_LEFT,
      IDR_FIND_DIALOG_MIDDLE,
      IDR_FIND_DIALOG_RIGHT,
      0, 0, 0, 0, 0, 0);
  }

  return dialog_border;
}

// Like gtk_util::CreateGtkBorderBin, but allows control over the alignment and
// returns both the event box and the alignment so we can modify it during its
// lifetime (i.e. during a theme change).
void BuildBorder(GtkWidget* child,
                 int padding_top, int padding_bottom, int padding_left,
                 int padding_right,
                 GtkWidget** ebox, GtkWidget** alignment) {
  *ebox = gtk_event_box_new();
  *alignment = gtk_alignment_new(0.0, 0.0, 1.0, 1.0);
  gtk_alignment_set_padding(GTK_ALIGNMENT(*alignment),
                            padding_top, padding_bottom, padding_left,
                            padding_right);
  gtk_container_add(GTK_CONTAINER(*alignment), child);
  gtk_container_add(GTK_CONTAINER(*ebox), *alignment);
}

}  // namespace

FindBarGtk::FindBarGtk(BrowserWindowGtk* window)
    : browser_(window->browser()),
      window_(window),
      theme_service_(GtkThemeService::GetFrom(browser_->profile())),
      container_width_(-1),
      container_height_(-1),
      match_label_failure_(false),
      ignore_changed_signal_(false) {
  InitWidgets();
  ViewIDUtil::SetID(text_entry_, VIEW_ID_FIND_IN_PAGE_TEXT_FIELD);

  // Insert the widget into the browser gtk hierarchy.
  window_->AddFindBar(this);

  // Hook up signals after the widget has been added to the hierarchy so the
  // widget will be realized.
  g_signal_connect(text_entry_, "changed",
                   G_CALLBACK(OnChanged), this);
  g_signal_connect_after(text_entry_, "key-press-event",
                         G_CALLBACK(OnKeyPressEvent), this);
  g_signal_connect_after(text_entry_, "key-release-event",
                         G_CALLBACK(OnKeyReleaseEvent), this);
  // When the user tabs to us or clicks on us, save where the focus used to
  // be.
  g_signal_connect(text_entry_, "focus",
                   G_CALLBACK(OnFocus), this);
  gtk_widget_add_events(text_entry_, GDK_BUTTON_PRESS_MASK);
  g_signal_connect(text_entry_, "button-press-event",
                   G_CALLBACK(OnButtonPress), this);
  g_signal_connect(text_entry_, "move-cursor", G_CALLBACK(OnMoveCursor), this);
  g_signal_connect(text_entry_, "activate", G_CALLBACK(OnActivateThunk), this);
  g_signal_connect(text_entry_, "direction-changed",
                   G_CALLBACK(OnWidgetDirectionChanged), this);
  g_signal_connect(text_entry_, "focus-in-event",
                   G_CALLBACK(OnFocusInThunk), this);
  g_signal_connect(text_entry_, "focus-out-event",
                   G_CALLBACK(OnFocusOutThunk), this);
  g_signal_connect(container_, "expose-event",
                   G_CALLBACK(OnExpose), this);
}

FindBarGtk::~FindBarGtk() {
}

void FindBarGtk::InitWidgets() {
  // The find bar is basically an hbox with a gtkentry (text box) followed by 3
  // buttons (previous result, next result, close).  We wrap the hbox in a gtk
  // alignment and a gtk event box to get the padding and light blue
  // background. We put that event box in a fixed in order to control its
  // lateral position. We put that fixed in a SlideAnimatorGtk in order to get
  // the slide effect.
  GtkWidget* hbox = gtk_hbox_new(false, 0);
  container_ = gtk_util::CreateGtkBorderBin(hbox, NULL,
      kBarPaddingTop, kBarPaddingBottom,
      kEntryPaddingLeft, kBarPaddingRight);
  gtk_widget_set_size_request(container_, kFindBarWidth, kFindBarHeight);
  ViewIDUtil::SetID(container_, VIEW_ID_FIND_IN_PAGE);
  gtk_widget_set_app_paintable(container_, TRUE);

  slide_widget_.reset(new SlideAnimatorGtk(container_,
                                           SlideAnimatorGtk::DOWN,
                                           0, false, true, NULL));

  GtkWidget* close_alignment = gtk_alignment_new(0, 0.6, 1, 0);
  close_button_.reset(CustomDrawButton::CloseButtonBar(theme_service_));
  gtk_container_add(GTK_CONTAINER(close_alignment), close_button_->widget());
  gtk_box_pack_end(GTK_BOX(hbox), close_alignment, FALSE, FALSE,
                   kCloseButtonPadding);
  g_signal_connect(close_button_->widget(), "clicked",
                   G_CALLBACK(OnClickedThunk), this);
  gtk_widget_set_tooltip_text(close_button_->widget(),
      l10n_util::GetStringUTF8(IDS_FIND_IN_PAGE_CLOSE_TOOLTIP).c_str());

  find_next_button_.reset(new CustomDrawButton(theme_service_,
      IDR_FINDINPAGE_NEXT, IDR_FINDINPAGE_NEXT_H, IDR_FINDINPAGE_NEXT_H,
      IDR_FINDINPAGE_NEXT_D, GTK_STOCK_GO_DOWN, GTK_ICON_SIZE_MENU));
  g_signal_connect(find_next_button_->widget(), "clicked",
                   G_CALLBACK(OnClickedThunk), this);
  gtk_widget_set_tooltip_text(find_next_button_->widget(),
      l10n_util::GetStringUTF8(IDS_FIND_IN_PAGE_NEXT_TOOLTIP).c_str());
  gtk_box_pack_end(GTK_BOX(hbox), find_next_button_->widget(),
                   FALSE, FALSE, 0);

  find_previous_button_.reset(new CustomDrawButton(theme_service_,
      IDR_FINDINPAGE_PREV, IDR_FINDINPAGE_PREV_H, IDR_FINDINPAGE_PREV_H,
      IDR_FINDINPAGE_PREV_D, GTK_STOCK_GO_UP, GTK_ICON_SIZE_MENU));
  g_signal_connect(find_previous_button_->widget(), "clicked",
                   G_CALLBACK(OnClickedThunk), this);
  gtk_widget_set_tooltip_text(find_previous_button_->widget(),
      l10n_util::GetStringUTF8(IDS_FIND_IN_PAGE_PREVIOUS_TOOLTIP).c_str());
  gtk_box_pack_end(GTK_BOX(hbox), find_previous_button_->widget(),
                   FALSE, FALSE, 0);

  // Make a box for the edit and match count widgets.
  GtkWidget* content_hbox = gtk_hbox_new(FALSE, 0);

  text_entry_ = gtk_entry_new();
  gtk_entry_set_has_frame(GTK_ENTRY(text_entry_), FALSE);

  match_count_label_ = gtk_label_new(NULL);
  // This line adds padding on the sides so that the label has even padding on
  // all edges.
  gtk_misc_set_padding(GTK_MISC(match_count_label_), 2, 0);
  match_count_event_box_ = gtk_event_box_new();
  GtkWidget* match_count_centerer = gtk_vbox_new(FALSE, 0);
  gtk_box_pack_start(GTK_BOX(match_count_centerer), match_count_event_box_,
                     TRUE, TRUE, 0);
  gtk_container_set_border_width(GTK_CONTAINER(match_count_centerer), 1);
  gtk_container_add(GTK_CONTAINER(match_count_event_box_), match_count_label_);

  gtk_box_pack_end(GTK_BOX(content_hbox), match_count_centerer,
                   FALSE, FALSE, 0);
  gtk_box_pack_end(GTK_BOX(content_hbox), text_entry_, TRUE, TRUE, 0);

  // This event box is necessary to color in the area above and below the match
  // count label, and is where we draw the entry background onto in GTK mode.
  BuildBorder(content_hbox, 0, 0, 0, 0,
              &content_event_box_, &content_alignment_);
  gtk_widget_set_app_paintable(content_event_box_, TRUE);
  g_signal_connect(content_event_box_, "expose-event",
                   G_CALLBACK(OnContentEventBoxExpose), this);

  // This alignment isn't centered and is used for spacing in chrome theme
  // mode. (It's also used in GTK mode for padding because left padding doesn't
  // equal bottom padding naturally.)
  BuildBorder(content_event_box_, 2, 2, 2, 0,
              &border_bin_, &border_bin_alignment_);
  gtk_box_pack_end(GTK_BOX(hbox), border_bin_, TRUE, TRUE, 0);

  theme_service_->InitThemesFor(this);
  registrar_.Add(this, chrome::NOTIFICATION_BROWSER_THEME_CHANGED,
                 content::Source<ThemeService>(theme_service_));

  g_signal_connect(widget(), "parent-set", G_CALLBACK(OnParentSet), this);

  // We take care to avoid showing the slide animator widget.
  gtk_widget_show_all(container_);
  gtk_widget_show(widget());
}

FindBarController* FindBarGtk::GetFindBarController() const {
  return find_bar_controller_;
}

void FindBarGtk::SetFindBarController(FindBarController* find_bar_controller) {
  find_bar_controller_ = find_bar_controller;
}

void FindBarGtk::Show(bool animate) {
  if (animate) {
    slide_widget_->Open();
    selection_rect_ = gfx::Rect();
    Reposition();
    GdkWindow* gdk_window = gtk_widget_get_window(container_);
    if (gdk_window)
      gdk_window_raise(gdk_window);
  } else {
    slide_widget_->OpenWithoutAnimation();
  }
}

void FindBarGtk::Hide(bool animate) {
  if (animate)
    slide_widget_->Close();
  else
    slide_widget_->CloseWithoutAnimation();
}

void FindBarGtk::SetFocusAndSelection() {
  StoreOutsideFocus();
  gtk_widget_grab_focus(text_entry_);
  // Select all the text.
  gtk_editable_select_region(GTK_EDITABLE(text_entry_), 0, -1);
}

void FindBarGtk::ClearResults(const FindNotificationDetails& results) {
  UpdateUIForFindResult(results, base::string16());
}

void FindBarGtk::StopAnimation() {
  slide_widget_->End();
}

void FindBarGtk::MoveWindowIfNecessary(const gfx::Rect& selection_rect,
                                       bool no_redraw) {
  // Not moving the window on demand, so do nothing.
}

void FindBarGtk::SetFindTextAndSelectedRange(const base::string16& find_text,
                                             const gfx::Range& selected_range) {
  std::string find_text_utf8 = base::UTF16ToUTF8(find_text);

  // Ignore the "changed" signal handler because programatically setting the
  // text should not fire a "changed" event.
  ignore_changed_signal_ = true;
  gtk_entry_set_text(GTK_ENTRY(text_entry_), find_text_utf8.c_str());
  if (selected_range.IsValid()) {
    gtk_editable_select_region(GTK_EDITABLE(text_entry_),
                               selected_range.start(), selected_range.end());
  }
  ignore_changed_signal_ = false;
}

base::string16 FindBarGtk::GetFindText() {
  std::string contents(gtk_entry_get_text(GTK_ENTRY(text_entry_)));
  return base::UTF8ToUTF16(contents);
}

gfx::Range FindBarGtk::GetSelectedRange() {
  gint start, end;
  gtk_editable_get_selection_bounds(GTK_EDITABLE(text_entry_), &start, &end);
  return gfx::Range(start, end);
}

void FindBarGtk::UpdateUIForFindResult(const FindNotificationDetails& result,
                                       const base::string16& find_text) {
  selection_rect_ = result.selection_rect();
  int xposition = GetDialogPosition(result.selection_rect()).x();
  GtkAllocation allocation;
  gtk_widget_get_allocation(widget(), &allocation);
  if (xposition != allocation.x)
    Reposition();

  // Once we find a match we no longer want to keep track of what had
  // focus. EndFindSession will then set the focus to the page content.
  if (result.number_of_matches() > 0)
    focus_store_.Store(NULL);

  std::string find_text_utf8 = base::UTF16ToUTF8(find_text);
  bool have_valid_range =
      result.number_of_matches() != -1 && result.active_match_ordinal() != -1;

  std::string entry_text(gtk_entry_get_text(GTK_ENTRY(text_entry_)));
  if (entry_text != find_text_utf8)
    SetFindTextAndSelectedRange(find_text, gfx::Range(0, find_text.length()));

  if (!find_text.empty() && have_valid_range) {
    gtk_label_set_text(GTK_LABEL(match_count_label_),
        l10n_util::GetStringFUTF8(IDS_FIND_IN_PAGE_COUNT,
            base::IntToString16(result.active_match_ordinal()),
            base::IntToString16(result.number_of_matches())).c_str());
    UpdateMatchLabelAppearance(result.number_of_matches() == 0 &&
                               result.final_update());
  } else {
    // If there was no text entered, we don't show anything in the result count
    // area.
    gtk_label_set_text(GTK_LABEL(match_count_label_), "");
    UpdateMatchLabelAppearance(false);
  }
}

void FindBarGtk::AudibleAlert() {
  // This call causes a lot of weird bugs, especially when using the custom
  // frame. TODO(estade): if people complain, re-enable it. See
  // http://crbug.com/27635 and others.
  //
  //   gtk_widget_error_bell(widget());
}

gfx::Rect FindBarGtk::GetDialogPosition(
    const gfx::Rect& avoid_overlapping_rect) {
  bool ltr = !base::i18n::IsRTL();
  // 15 is the size of the scrollbar, copied from ScrollbarThemeChromium.
  // The height is not used.
  // At very low browser widths we can wind up with a negative |dialog_bounds|
  // width, so clamp it to 0.
  GtkAllocation parent_allocation;
  gtk_widget_get_allocation(gtk_widget_get_parent(widget()),
                            &parent_allocation);
  gfx::Rect dialog_bounds = gfx::Rect(ltr ? 0 : 15, 0,
      std::max(0, parent_allocation.width - (ltr ? 15 : 0)), 0);

  GtkRequisition req;
  gtk_widget_size_request(container_, &req);
  gfx::Size prefsize(req.width, req.height);

  gfx::Rect view_location(
      ltr ? dialog_bounds.width() - prefsize.width() : dialog_bounds.x(),
      dialog_bounds.y(), prefsize.width(), prefsize.height());
  gfx::Rect new_pos = FindBarController::GetLocationForFindbarView(
      view_location, dialog_bounds, avoid_overlapping_rect);

  return new_pos;
}

bool FindBarGtk::IsFindBarVisible() {
  return gtk_widget_get_visible(widget());
}

void FindBarGtk::RestoreSavedFocus() {
  // This function sometimes gets called when we don't have focus. We should do
  // nothing in this case.
  if (!gtk_widget_is_focus(text_entry_))
    return;

  if (focus_store_.widget())
    gtk_widget_grab_focus(focus_store_.widget());
  else
    find_bar_controller_->web_contents()->GetView()->Focus();
}

bool FindBarGtk::HasGlobalFindPasteboard() {
  return false;
}

void FindBarGtk::UpdateFindBarForChangedWebContents() {
}

FindBarTesting* FindBarGtk::GetFindBarTesting() {
  return this;
}

void FindBarGtk::Observe(int type,
                         const content::NotificationSource& source,
                         const content::NotificationDetails& details) {
  DCHECK_EQ(type, chrome::NOTIFICATION_BROWSER_THEME_CHANGED);

  // Force reshapings of the find bar window.
  container_width_ = -1;
  container_height_ = -1;

  if (theme_service_->UsingNativeTheme()) {
    gtk_widget_modify_cursor(text_entry_, NULL, NULL);
    gtk_widget_modify_base(text_entry_, GTK_STATE_NORMAL, NULL);
    gtk_widget_modify_text(text_entry_, GTK_STATE_NORMAL, NULL);

    // Prevent forced font sizes because it causes the jump up and down
    // character movement (http://crbug.com/22614), and because it will
    // prevent centering of the text entry.
    gtk_util::UndoForceFontSize(text_entry_);
    gtk_util::UndoForceFontSize(match_count_label_);

    gtk_widget_set_size_request(content_event_box_, -1, -1);
    gtk_widget_modify_bg(content_event_box_, GTK_STATE_NORMAL, NULL);

    // Replicate the normal GtkEntry behaviour by drawing the entry
    // background. We set the fake alignment to be the frame thickness.
    GtkStyle* style = gtk_rc_get_style(text_entry_);
    gint xborder = style->xthickness;
    gint yborder = style->ythickness;
    gtk_alignment_set_padding(GTK_ALIGNMENT(content_alignment_),
                              yborder, yborder, xborder, xborder);

    // We leave left padding on the left, even in GTK mode, as it's required
    // for the left margin to be equivalent to the bottom margin.
    gtk_alignment_set_padding(GTK_ALIGNMENT(border_bin_alignment_),
                              0, 0, 1, 0);

    // We need this event box to have its own window in GTK mode for doing the
    // hacky widget rendering.
    gtk_event_box_set_visible_window(GTK_EVENT_BOX(border_bin_), TRUE);
    gtk_widget_set_app_paintable(border_bin_, TRUE);

    gtk_misc_set_alignment(GTK_MISC(match_count_label_), 0.5, 0.5);
  } else {
    gtk_widget_modify_cursor(text_entry_, &ui::kGdkBlack, &ui::kGdkGray);
    gtk_widget_modify_base(text_entry_, GTK_STATE_NORMAL,
                           &kEntryBackgroundColor);
    gtk_widget_modify_text(text_entry_, GTK_STATE_NORMAL, &kEntryTextColor);

    // Until we switch to vector graphics, force the font size.
    gtk_util::ForceFontSizePixels(text_entry_, 13.4);  // 13.4px == 10pt @ 96dpi
    gtk_util::ForceFontSizePixels(match_count_label_, 13.4);

    // Force the text widget height so it lines up with the buttons regardless
    // of font size.
    gtk_widget_set_size_request(content_event_box_, -1, 20);
    gtk_widget_modify_bg(content_event_box_, GTK_STATE_NORMAL,
                         &kEntryBackgroundColor);

    gtk_alignment_set_padding(GTK_ALIGNMENT(content_alignment_),
                              0.0, 0.0, 0.0, 0.0);

    gtk_alignment_set_padding(GTK_ALIGNMENT(border_bin_alignment_),
                              2, 2, 3, 0);

    // We need this event box to be invisible because we're only going to draw
    // on the background (but we can't take it out of the heiarchy entirely
    // because we also need it to take up space).
    gtk_event_box_set_visible_window(GTK_EVENT_BOX(border_bin_), FALSE);
    gtk_widget_set_app_paintable(border_bin_, FALSE);

    gtk_misc_set_alignment(GTK_MISC(match_count_label_), 0.5, 1.0);

    // This is necessary to make the close button dark enough.
    ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
    close_button_->SetBackground(
        theme_service_->GetColor(ThemeProperties::COLOR_TAB_TEXT),
        rb.GetImageNamed(IDR_CLOSE_1).AsBitmap(),
        rb.GetImageNamed(IDR_CLOSE_1).AsBitmap());
  }

  UpdateMatchLabelAppearance(match_label_failure_);
}

bool FindBarGtk::GetFindBarWindowInfo(gfx::Point* position,
                                      bool* fully_visible) {
  if (position)
    *position = GetPosition();

  if (fully_visible) {
    *fully_visible = !slide_widget_->IsAnimating() &&
                     slide_widget_->IsShowing();
  }
  return true;
}

base::string16 FindBarGtk::GetFindSelectedText() {
  gint cursor_pos;
  gint selection_bound;
  g_object_get(G_OBJECT(text_entry_), "cursor-position", &cursor_pos,
               NULL);
  g_object_get(G_OBJECT(text_entry_), "selection-bound", &selection_bound,
               NULL);
  std::string contents(gtk_entry_get_text(GTK_ENTRY(text_entry_)));
  return base::UTF8ToUTF16(contents.substr(cursor_pos, selection_bound));
}

base::string16 FindBarGtk::GetMatchCountText() {
  std::string contents(gtk_label_get_text(GTK_LABEL(match_count_label_)));
  return base::UTF8ToUTF16(contents);
}

int FindBarGtk::GetWidth() {
  GtkAllocation allocation;
  gtk_widget_get_allocation(container_, &allocation);
  return allocation.width;
}

void FindBarGtk::FindEntryTextInContents(bool forward_search) {
  content::WebContents* web_contents = find_bar_controller_->web_contents();
  if (!web_contents)
    return;
  FindTabHelper* find_tab_helper = FindTabHelper::FromWebContents(web_contents);

  std::string new_contents(gtk_entry_get_text(GTK_ENTRY(text_entry_)));

  if (new_contents.length() > 0) {
    find_tab_helper->StartFinding(base::UTF8ToUTF16(new_contents),
                                  forward_search,
                                  false);  // Not case sensitive.
  } else {
    // The textbox is empty so we reset.
    find_tab_helper->StopFinding(FindBarController::kClearSelectionOnPage);
    UpdateUIForFindResult(find_tab_helper->find_result(), base::string16());

    // Clearing the text box should also clear the prepopulate state so that
    // when we close and reopen the Find box it doesn't show the search we
    // just deleted.
    FindBarState* find_bar_state = FindBarStateFactory::GetForProfile(
        browser_->profile());
    find_bar_state->set_last_prepopulate_text(base::string16());
  }
}

void FindBarGtk::UpdateMatchLabelAppearance(bool failure) {
  match_label_failure_ = failure;
  bool use_gtk = theme_service_->UsingNativeTheme();

  if (use_gtk) {
    GtkStyle* style = gtk_rc_get_style(text_entry_);
    GdkColor normal_bg = style->base[GTK_STATE_NORMAL];
    GdkColor normal_text = gtk_util::AverageColors(
        style->text[GTK_STATE_NORMAL], style->base[GTK_STATE_NORMAL]);

    gtk_widget_modify_bg(match_count_event_box_, GTK_STATE_NORMAL,
                         failure ? &kFindFailureBackgroundColor :
                         &normal_bg);
    gtk_widget_modify_fg(match_count_label_, GTK_STATE_NORMAL,
                         failure ? &kEntryTextColor : &normal_text);
  } else {
    gtk_widget_modify_bg(match_count_event_box_, GTK_STATE_NORMAL,
                         failure ? &kFindFailureBackgroundColor :
                         &kEntryBackgroundColor);
    gtk_widget_modify_fg(match_count_label_, GTK_STATE_NORMAL,
                         failure ? &kEntryTextColor : &kFindSuccessTextColor);
  }
}

void FindBarGtk::Reposition() {
  if (!IsFindBarVisible())
    return;

  // This will trigger an allocate, which allows us to reposition.
  GtkWidget* parent = gtk_widget_get_parent(widget());
  if (parent)
    gtk_widget_queue_resize(parent);
}

void FindBarGtk::StoreOutsideFocus() {
  // |text_entry_| is the only widget in the find bar that can be focused,
  // so it's the only one we have to check.
  // TODO(estade): when we make the find bar buttons focusable, we'll have
  // to change this (same above in RestoreSavedFocus).
  if (!gtk_widget_is_focus(text_entry_))
    focus_store_.Store(text_entry_);
}

bool FindBarGtk::MaybeForwardKeyEventToRenderer(GdkEventKey* event) {
  switch (event->keyval) {
    case GDK_Down:
    case GDK_Up:
    case GDK_Page_Up:
    case GDK_Page_Down:
      break;
    case GDK_Home:
    case GDK_End:
      if ((event->state & gtk_accelerator_get_default_mod_mask()) ==
          GDK_CONTROL_MASK) {
        break;
      }
    // Fall through.
    default:
      return false;
  }

  content::WebContents* contents = find_bar_controller_->web_contents();
  if (!contents)
    return false;

  content::RenderViewHost* render_view_host = contents->GetRenderViewHost();

  // Make sure we don't have a text field element interfering with keyboard
  // input. Otherwise Up and Down arrow key strokes get eaten. "Nom Nom Nom".
  render_view_host->ClearFocusedElement();

  NativeWebKeyboardEvent wke(reinterpret_cast<GdkEvent*>(event));
  render_view_host->ForwardKeyboardEvent(wke);
  return true;
}

void FindBarGtk::AdjustTextAlignment() {
  PangoDirection content_dir =
      pango_find_base_dir(gtk_entry_get_text(GTK_ENTRY(text_entry_)), -1);

  GtkTextDirection widget_dir = gtk_widget_get_direction(text_entry_);

  // Use keymap or widget direction if content does not have strong direction.
  // It matches the behavior of GtkEntry.
  if (content_dir == PANGO_DIRECTION_NEUTRAL) {
    if (gtk_widget_has_focus(text_entry_)) {
      content_dir = gdk_keymap_get_direction(
        gdk_keymap_get_for_display(gtk_widget_get_display(text_entry_)));
    } else {
      if (widget_dir == GTK_TEXT_DIR_RTL)
        content_dir = PANGO_DIRECTION_RTL;
      else
        content_dir = PANGO_DIRECTION_LTR;
    }
  }

  if ((widget_dir == GTK_TEXT_DIR_RTL && content_dir == PANGO_DIRECTION_LTR) ||
      (widget_dir == GTK_TEXT_DIR_LTR && content_dir == PANGO_DIRECTION_RTL)) {
    gtk_entry_set_alignment(GTK_ENTRY(text_entry_), 1.0);
  } else {
    gtk_entry_set_alignment(GTK_ENTRY(text_entry_), 0.0);
  }
}

gfx::Point FindBarGtk::GetPosition() {
  gfx::Point point;

  GtkWidget* parent = gtk_widget_get_parent(widget());

  GValue value = { 0, };
  g_value_init(&value, G_TYPE_INT);
  gtk_container_child_get_property(GTK_CONTAINER(parent),
                                   widget(), "x", &value);
  point.set_x(g_value_get_int(&value));

  gtk_container_child_get_property(GTK_CONTAINER(parent),
                                   widget(), "y", &value);
  point.set_y(g_value_get_int(&value));

  g_value_unset(&value);

  return point;
}

// static
void FindBarGtk::OnParentSet(GtkWidget* widget, GtkObject* old_parent,
                             FindBarGtk* find_bar) {
  if (!gtk_widget_get_parent(widget))
    return;

  g_signal_connect(gtk_widget_get_parent(widget), "set-floating-position",
                   G_CALLBACK(OnSetFloatingPosition), find_bar);
}

// static
void FindBarGtk::OnSetFloatingPosition(GtkFloatingContainer* floating_container,
                                       GtkAllocation* allocation,
                                       FindBarGtk* find_bar) {
  GtkWidget* findbar = find_bar->widget();

  int xposition = find_bar->GetDialogPosition(find_bar->selection_rect_).x();

  GValue value = { 0, };
  g_value_init(&value, G_TYPE_INT);
  g_value_set_int(&value, xposition);
  gtk_container_child_set_property(GTK_CONTAINER(floating_container),
                                   findbar, "x", &value);

  g_value_set_int(&value, 0);
  gtk_container_child_set_property(GTK_CONTAINER(floating_container),
                                   findbar, "y", &value);
  g_value_unset(&value);
}

// static
gboolean FindBarGtk::OnChanged(GtkWindow* window, FindBarGtk* find_bar) {
  find_bar->AdjustTextAlignment();

  if (!find_bar->ignore_changed_signal_)
    find_bar->FindEntryTextInContents(true);

  return FALSE;
}

// static
gboolean FindBarGtk::OnKeyPressEvent(GtkWidget* widget, GdkEventKey* event,
                                     FindBarGtk* find_bar) {
  if (find_bar->MaybeForwardKeyEventToRenderer(event)) {
    return TRUE;
  } else if (GDK_Escape == event->keyval) {
    find_bar->find_bar_controller_->EndFindSession(
        FindBarController::kKeepSelectionOnPage,
        FindBarController::kKeepResultsInFindBox);
    return TRUE;
  } else if (GDK_Return == event->keyval ||
             GDK_KP_Enter == event->keyval) {
    if ((event->state & gtk_accelerator_get_default_mod_mask()) ==
        GDK_CONTROL_MASK) {
      find_bar->find_bar_controller_->EndFindSession(
          FindBarController::kActivateSelectionOnPage,
          FindBarController::kClearResultsInFindBox);
      return TRUE;
    }

    bool forward = (event->state & gtk_accelerator_get_default_mod_mask()) !=
                   GDK_SHIFT_MASK;
    find_bar->FindEntryTextInContents(forward);
    return TRUE;
  } else if (GDK_F3 == event->keyval) {
    // There is a bug in GTK+ version available with Ubuntu 12.04 which causes
    // Shift+Fn key combination getting registered as Fn when used with
    // GTK accelerators. And this broke the search backward functionality with
    // Shift+F3. This is a workaround to fix the search functionality till we
    // have the GTK+ fix available. The GTK+ issue is being tracked under
    // https://bugzilla.gnome.org/show_bug.cgi?id=661973
    bool forward = (event->state & gtk_accelerator_get_default_mod_mask()) !=
                   GDK_SHIFT_MASK;
    find_bar->FindEntryTextInContents(forward);
    return TRUE;
  }
  return FALSE;
}

// static
gboolean FindBarGtk::OnKeyReleaseEvent(GtkWidget* widget, GdkEventKey* event,
                                       FindBarGtk* find_bar) {
  return find_bar->MaybeForwardKeyEventToRenderer(event);
}

void FindBarGtk::OnClicked(GtkWidget* button) {
  if (button == close_button_->widget()) {
    find_bar_controller_->EndFindSession(
        FindBarController::kKeepSelectionOnPage,
        FindBarController::kKeepResultsInFindBox);
  } else if (button == find_previous_button_->widget() ||
             button == find_next_button_->widget()) {
    FindEntryTextInContents(button == find_next_button_->widget());
  } else {
    NOTREACHED();
  }
}

// static
gboolean FindBarGtk::OnContentEventBoxExpose(GtkWidget* widget,
                                             GdkEventExpose* event,
                                             FindBarGtk* bar) {
  TRACE_EVENT0("ui::gtk", "FindBarGtk::OnContentEventBoxExpose");
  if (bar->theme_service_->UsingNativeTheme()) {
    // Draw the text entry background around where we input stuff. Note the
    // decrement to |width|. We do this because some theme engines
    // (*cough*Clearlooks*cough*) don't do any blending and use thickness to
    // make sure that widgets never overlap.
    int padding = gtk_widget_get_style(widget)->xthickness;
    GdkRectangle rec;
    gtk_widget_get_allocation(widget, &rec);
    rec.width -= padding;

    gtk_util::DrawTextEntryBackground(bar->text_entry_, widget,
                                      &event->area, &rec);
  }

  return FALSE;
}

// Used to handle custom painting of |container_|.
gboolean FindBarGtk::OnExpose(GtkWidget* widget, GdkEventExpose* e,
                              FindBarGtk* bar) {
  TRACE_EVENT0("ui::gtk", "FindBarGtk::OnExpose");

  GtkAllocation allocation;
  gtk_widget_get_allocation(widget, &allocation);

  if (bar->theme_service_->UsingNativeTheme()) {
    if (bar->container_width_ != allocation.width ||
        bar->container_height_ != allocation.height) {
      std::vector<GdkPoint> mask_points = MakeFramePolygonPoints(
          allocation.width, allocation.height, FRAME_MASK);
      GdkRegion* mask_region = gdk_region_polygon(&mask_points[0],
                                                  mask_points.size(),
                                                  GDK_EVEN_ODD_RULE);
      // Reset the shape.
      GdkWindow* gdk_window = gtk_widget_get_window(widget);
      gdk_window_shape_combine_region(gdk_window, NULL, 0, 0);
      gdk_window_shape_combine_region(gdk_window, mask_region, 0, 0);
      gdk_region_destroy(mask_region);

      bar->container_width_ = allocation.width;
      bar->container_height_ = allocation.height;
    }

    GdkDrawable* drawable = GDK_DRAWABLE(e->window);
    GdkGC* gc = gdk_gc_new(drawable);
    gdk_gc_set_clip_rectangle(gc, &e->area);
    GdkColor color = bar->theme_service_->GetBorderColor();
    gdk_gc_set_rgb_fg_color(gc, &color);

    // Stroke the frame border.
    std::vector<GdkPoint> points = MakeFramePolygonPoints(
        allocation.width, allocation.height, FRAME_STROKE);
    gdk_draw_lines(drawable, gc, &points[0], points.size());

    g_object_unref(gc);
  } else {
    if (bar->container_width_ != allocation.width ||
        bar->container_height_ != allocation.height) {
      // Reset the shape.
      gdk_window_shape_combine_region(gtk_widget_get_window(widget),
                                      NULL, 0, 0);
      SetDialogShape(bar->container_);

      bar->container_width_ = allocation.width;
      bar->container_height_ = allocation.height;
    }

    cairo_t* cr = gdk_cairo_create(gtk_widget_get_window(widget));
    gdk_cairo_rectangle(cr, &e->area);
    cairo_clip(cr);

    gfx::Point tabstrip_origin =
        bar->window_->tabstrip()->GetTabStripOriginForWidget(widget);

    gtk_util::DrawThemedToolbarBackground(widget, cr, e, tabstrip_origin,
                                          bar->theme_service_);

    // During chrome theme mode, we need to draw the border around content_hbox
    // now instead of when we render |border_bin_|. We don't use stacked event
    // boxes to simulate the effect because we need to blend them with this
    // background.
    GtkAllocation border_allocation;
    gtk_widget_get_allocation(bar->border_bin_, &border_allocation);

    // Blit the left part of the background image once on the left.
    ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();

    gfx::CairoCachedSurface* background_left = rb.GetNativeImageNamed(
        IDR_FIND_BOX_BACKGROUND_LEFT,
        ui::ResourceBundle::RTL_ENABLED).ToCairo();
    background_left->SetSource(cr, widget,
                               border_allocation.x, border_allocation.y);
    cairo_pattern_set_extend(cairo_get_source(cr), CAIRO_EXTEND_REPEAT);
    cairo_rectangle(cr, border_allocation.x, border_allocation.y,
                    background_left->Width(), background_left->Height());
    cairo_fill(cr);

    // Blit the center part of the background image in all the space between.
    gfx::CairoCachedSurface* background =
        rb.GetNativeImageNamed(IDR_FIND_BOX_BACKGROUND).ToCairo();
    background->SetSource(cr, widget,
                          border_allocation.x + background_left->Width(),
                          border_allocation.y);
    cairo_pattern_set_extend(cairo_get_source(cr), CAIRO_EXTEND_REPEAT);
    cairo_rectangle(cr,
                    border_allocation.x + background_left->Width(),
                    border_allocation.y,
                    border_allocation.width - background_left->Width(),
                    background->Height());
    cairo_fill(cr);

    cairo_destroy(cr);

    // Draw the border.
    GetDialogBorder()->RenderToWidget(widget);
  }

  // Propagate to the container's child.
  GtkWidget* child = gtk_bin_get_child(GTK_BIN(widget));
  if (child)
    gtk_container_propagate_expose(GTK_CONTAINER(widget), child, e);
  return TRUE;
}

// static
gboolean FindBarGtk::OnFocus(GtkWidget* text_entry, GtkDirectionType focus,
                             FindBarGtk* find_bar) {
  find_bar->StoreOutsideFocus();

  // Continue propagating the event.
  return FALSE;
}

// static
gboolean FindBarGtk::OnButtonPress(GtkWidget* text_entry, GdkEventButton* e,
                                   FindBarGtk* find_bar) {
  find_bar->StoreOutsideFocus();

  // Continue propagating the event.
  return FALSE;
}

// static
void FindBarGtk::OnMoveCursor(GtkEntry* entry, GtkMovementStep step, gint count,
                              gboolean selection, FindBarGtk* bar) {
  static guint signal_id = g_signal_lookup("move-cursor", GTK_TYPE_ENTRY);

  GdkEvent* event = gtk_get_current_event();
  if (event) {
    if ((event->type == GDK_KEY_PRESS || event->type == GDK_KEY_RELEASE) &&
        bar->MaybeForwardKeyEventToRenderer(&(event->key))) {
      g_signal_stop_emission(entry, signal_id, 0);
    }

    gdk_event_free(event);
  }
}

void FindBarGtk::OnActivate(GtkWidget* entry) {
  FindEntryTextInContents(true);
}

gboolean FindBarGtk::OnFocusIn(GtkWidget* entry, GdkEventFocus* event) {
  g_signal_connect(gdk_keymap_get_for_display(gtk_widget_get_display(entry)),
                   "direction-changed",
                   G_CALLBACK(&OnKeymapDirectionChanged), this);

  AdjustTextAlignment();

  return FALSE;  // Continue propagation.
}

gboolean FindBarGtk::OnFocusOut(GtkWidget* entry, GdkEventFocus* event) {
  g_signal_handlers_disconnect_by_func(
      gdk_keymap_get_for_display(gtk_widget_get_display(entry)),
      reinterpret_cast<gpointer>(&OnKeymapDirectionChanged), this);

  return FALSE;  // Continue propagation.
}

/* [<][>][^][v][top][bottom][index][help] */