Home | History | Annotate | Download | only in gtk
      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).
     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.
    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.
    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).
    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 
    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 
    217 FindBarGtk::~FindBarGtk() {
    218 }
    219 
    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 
    316 FindBarController* FindBarGtk::GetFindBarController() const {
    317   return find_bar_controller_;
    318 }
    319 
    320 void FindBarGtk::SetFindBarController(FindBarController* find_bar_controller) {
    321   find_bar_controller_ = find_bar_controller;
    322 }
    323 
    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 
    336 void FindBarGtk::Hide(bool animate) {
    337   if (animate)
    338     slide_widget_->Close();
    339   else
    340     slide_widget_->CloseWithoutAnimation();
    341 }
    342 
    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 
    350 void FindBarGtk::ClearResults(const FindNotificationDetails& results) {
    351   UpdateUIForFindResult(results, string16());
    352 }
    353 
    354 void FindBarGtk::StopAnimation() {
    355   slide_widget_->End();
    356 }
    357 
    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 
    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 
    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 
    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 
    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 
    442 bool FindBarGtk::IsFindBarVisible() {
    443   return GTK_WIDGET_VISIBLE(widget());
    444 }
    445 
    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 
    458 FindBarTesting* FindBarGtk::GetFindBarTesting() {
    459   return this;
    460 }
    461 
    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 
    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 
    558 string16 FindBarGtk::GetFindText() {
    559   std::string contents(gtk_entry_get_text(GTK_ENTRY(text_entry_)));
    560   return UTF8ToUTF16(contents);
    561 }
    562 
    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 
    574 string16 FindBarGtk::GetMatchCountText() {
    575   std::string contents(gtk_label_get_text(GTK_LABEL(match_count_label_)));
    576   return UTF8ToUTF16(contents);
    577 }
    578 
    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 
    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 
    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 
    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 
    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 
    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 
    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
    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
    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
    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
    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
    792 gboolean FindBarGtk::OnKeyReleaseEvent(GtkWidget* widget, GdkEventKey* event,
    793                                        FindBarGtk* find_bar) {
    794   return find_bar->MaybeForwardKeyEventToRenderer(event);
    795 }
    796 
    797 // static
    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
    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_|.
    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
    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
    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
    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
    970 void FindBarGtk::OnActivate(GtkEntry* entry, FindBarGtk* bar) {
    971   bar->FindEntryTextInContents(true);
    972 }
    973 
    974 // static
    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
    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