Home | History | Annotate | Download | only in autofill
      1 // Copyright (c) 2012 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/autofill/autofill_popup_view_gtk.h"
      6 
      7 #include <gdk/gdkkeysyms.h>
      8 
      9 #include "base/logging.h"
     10 #include "base/strings/utf_string_conversions.h"
     11 #include "chrome/browser/ui/autofill/autofill_popup_controller.h"
     12 #include "chrome/browser/ui/gtk/gtk_util.h"
     13 #include "grit/ui_resources.h"
     14 #include "third_party/WebKit/public/web/WebAutofillClient.h"
     15 #include "ui/base/gtk/gtk_compat.h"
     16 #include "ui/base/gtk/gtk_hig_constants.h"
     17 #include "ui/base/gtk/gtk_windowing.h"
     18 #include "ui/base/resource/resource_bundle.h"
     19 #include "ui/gfx/image/image.h"
     20 #include "ui/gfx/native_widget_types.h"
     21 #include "ui/gfx/pango_util.h"
     22 #include "ui/gfx/rect.h"
     23 
     24 using WebKit::WebAutofillClient;
     25 
     26 namespace {
     27 
     28 const GdkColor kBorderColor = GDK_COLOR_RGB(0xc7, 0xca, 0xce);
     29 const GdkColor kHoveredBackgroundColor = GDK_COLOR_RGB(0xcd, 0xcd, 0xcd);
     30 const GdkColor kNameColor = GDK_COLOR_RGB(0x00, 0x00, 0x00);
     31 const GdkColor kWarningColor = GDK_COLOR_RGB(0x7f, 0x7f, 0x7f);
     32 const GdkColor kSubtextColor = GDK_COLOR_RGB(0x7f, 0x7f, 0x7f);
     33 
     34 }  // namespace
     35 
     36 namespace autofill {
     37 
     38 AutofillPopupViewGtk::AutofillPopupViewGtk(
     39     AutofillPopupController* controller)
     40     : controller_(controller),
     41       window_(gtk_window_new(GTK_WINDOW_POPUP)) {
     42   gtk_window_set_resizable(GTK_WINDOW(window_), FALSE);
     43   gtk_widget_set_app_paintable(window_, TRUE);
     44   gtk_widget_set_double_buffered(window_, TRUE);
     45 
     46   // Setup the window to ensure it receives the expose event.
     47   gtk_widget_add_events(window_, GDK_BUTTON_MOTION_MASK |
     48                                  GDK_BUTTON_RELEASE_MASK |
     49                                  GDK_EXPOSURE_MASK |
     50                                  GDK_POINTER_MOTION_MASK);
     51 
     52   GtkWidget* toplevel_window = gtk_widget_get_toplevel(
     53       controller->container_view());
     54   signals_.Connect(toplevel_window, "configure-event",
     55                    G_CALLBACK(HandleConfigureThunk), this);
     56   g_signal_connect(window_, "expose-event",
     57                    G_CALLBACK(HandleExposeThunk), this);
     58   g_signal_connect(window_, "leave-notify-event",
     59                    G_CALLBACK(HandleLeaveThunk), this);
     60   g_signal_connect(window_, "motion-notify-event",
     61                    G_CALLBACK(HandleMotionThunk), this);
     62   g_signal_connect(window_, "button-release-event",
     63                    G_CALLBACK(HandleButtonReleaseThunk), this);
     64 
     65   // Cache the layout so we don't have to create it for every expose.
     66   layout_ = gtk_widget_create_pango_layout(window_, NULL);
     67 }
     68 
     69 AutofillPopupViewGtk::~AutofillPopupViewGtk() {
     70   g_object_unref(layout_);
     71   gtk_widget_destroy(window_);
     72 }
     73 
     74 void AutofillPopupViewGtk::Hide() {
     75   delete this;
     76 }
     77 
     78 void AutofillPopupViewGtk::Show() {
     79   UpdateBoundsAndRedrawPopup();
     80 
     81   gtk_widget_show(window_);
     82 
     83   GtkWidget* parent_window =
     84       gtk_widget_get_toplevel(controller_->container_view());
     85   ui::StackPopupWindow(window_, parent_window);
     86 }
     87 
     88 void AutofillPopupViewGtk::InvalidateRow(size_t row) {
     89   GdkRectangle row_rect = controller_->GetRowBounds(row).ToGdkRectangle();
     90   GdkWindow* gdk_window = gtk_widget_get_window(window_);
     91   gdk_window_invalidate_rect(gdk_window, &row_rect, FALSE);
     92 }
     93 
     94 void AutofillPopupViewGtk::UpdateBoundsAndRedrawPopup() {
     95   gtk_widget_set_size_request(window_,
     96                               controller_->popup_bounds().width(),
     97                               controller_->popup_bounds().height());
     98   gtk_window_move(GTK_WINDOW(window_),
     99                   controller_->popup_bounds().x(),
    100                   controller_->popup_bounds().y());
    101 
    102   GdkWindow* gdk_window = gtk_widget_get_window(window_);
    103   GdkRectangle popup_rect = controller_->popup_bounds().ToGdkRectangle();
    104   if (gdk_window != NULL)
    105     gdk_window_invalidate_rect(gdk_window, &popup_rect, FALSE);
    106 }
    107 
    108 gboolean AutofillPopupViewGtk::HandleConfigure(GtkWidget* widget,
    109                                                GdkEventConfigure* event) {
    110   controller_->Hide();
    111   return FALSE;
    112 }
    113 
    114 gboolean AutofillPopupViewGtk::HandleButtonRelease(GtkWidget* widget,
    115                                                    GdkEventButton* event) {
    116   // We only care about the left click.
    117   if (event->button != 1)
    118     return FALSE;
    119 
    120   controller_->MouseClicked(event->x, event->y);
    121   return TRUE;
    122 }
    123 
    124 gboolean AutofillPopupViewGtk::HandleExpose(GtkWidget* widget,
    125                                             GdkEventExpose* event) {
    126   cairo_t* cr = gdk_cairo_create(GDK_DRAWABLE(gtk_widget_get_window(widget)));
    127   gdk_cairo_rectangle(cr, &event->area);
    128   cairo_clip(cr);
    129 
    130   // This assert is kinda ugly, but it would be more currently unneeded work
    131   // to support painting a border that isn't 1 pixel thick.  There is no point
    132   // in writing that code now, and explode if that day ever comes.
    133   DCHECK_EQ(1, kBorderThickness);
    134   // Draw the 1px border around the entire window.
    135   gdk_cairo_set_source_color(cr, &kBorderColor);
    136   gdk_cairo_rectangle(cr, &widget->allocation);
    137   cairo_stroke(cr);
    138   SetUpLayout();
    139 
    140   gfx::Rect damage_rect(event->area);
    141 
    142   for (size_t i = 0; i < controller_->names().size(); ++i) {
    143     gfx::Rect line_rect = controller_->GetRowBounds(i);
    144     // Only repaint and layout damaged lines.
    145     if (!line_rect.Intersects(damage_rect))
    146       continue;
    147 
    148     if (controller_->identifiers()[i] == WebAutofillClient::MenuItemIDSeparator)
    149       DrawSeparator(cr, line_rect);
    150     else
    151       DrawAutofillEntry(cr, i, line_rect);
    152   }
    153 
    154   cairo_destroy(cr);
    155 
    156   return TRUE;
    157 }
    158 
    159 gboolean AutofillPopupViewGtk::HandleLeave(GtkWidget* widget,
    160                                            GdkEventCrossing* event) {
    161   controller_->MouseExitedPopup();
    162 
    163   return FALSE;
    164 }
    165 
    166 gboolean AutofillPopupViewGtk::HandleMotion(GtkWidget* widget,
    167                                             GdkEventMotion* event) {
    168   controller_->MouseHovered(event->x, event->y);
    169 
    170   return TRUE;
    171 }
    172 
    173 void AutofillPopupViewGtk::SetUpLayout() {
    174   pango_layout_set_width(layout_, window_->allocation.width * PANGO_SCALE);
    175   pango_layout_set_height(layout_, window_->allocation.height * PANGO_SCALE);
    176 }
    177 
    178 void AutofillPopupViewGtk::SetLayoutText(const string16& text,
    179                                          const gfx::Font& font,
    180                                          const GdkColor text_color) {
    181   PangoAttrList* attrs = pango_attr_list_new();
    182 
    183   PangoAttribute* fg_attr = pango_attr_foreground_new(text_color.red,
    184                                                       text_color.green,
    185                                                       text_color.blue);
    186   pango_attr_list_insert(attrs, fg_attr);  // Ownership taken.
    187 
    188   pango_layout_set_attributes(layout_, attrs);  // Ref taken.
    189   pango_attr_list_unref(attrs);
    190 
    191   gfx::ScopedPangoFontDescription font_description(font.GetNativeFont());
    192   pango_layout_set_font_description(layout_, font_description.get());
    193 
    194   gtk_util::SetLayoutText(layout_, text);
    195 
    196   // The popup is already the correct size for the text, so set the width to -1
    197   // to prevent additional wrapping or ellipsization.
    198   pango_layout_set_width(layout_, -1);
    199 }
    200 
    201 void AutofillPopupViewGtk::DrawSeparator(cairo_t* cairo_context,
    202                                          const gfx::Rect& separator_rect) {
    203   cairo_save(cairo_context);
    204   cairo_move_to(cairo_context, 0, separator_rect.y());
    205   cairo_line_to(cairo_context,
    206                 separator_rect.width(),
    207                 separator_rect.y() + separator_rect.height());
    208   cairo_stroke(cairo_context);
    209   cairo_restore(cairo_context);
    210 }
    211 
    212 void AutofillPopupViewGtk::DrawAutofillEntry(cairo_t* cairo_context,
    213                                              size_t index,
    214                                              const gfx::Rect& entry_rect) {
    215   if (controller_->selected_line() == static_cast<int>(index)) {
    216     gdk_cairo_set_source_color(cairo_context, &kHoveredBackgroundColor);
    217     cairo_rectangle(cairo_context, entry_rect.x(), entry_rect.y(),
    218                     entry_rect.width(), entry_rect.height());
    219     cairo_fill(cairo_context);
    220   }
    221 
    222   // Draw the value.
    223   SetLayoutText(controller_->names()[index],
    224                 controller_->GetNameFontForRow(index),
    225                 controller_->IsWarning(index) ? kWarningColor : kNameColor);
    226   int value_text_width = controller_->GetNameFontForRow(index).GetStringWidth(
    227       controller_->names()[index]);
    228 
    229   // Center the text within the line.
    230   int row_height = entry_rect.height();
    231   int value_content_y = std::max(
    232       entry_rect.y(),
    233       entry_rect.y() +
    234           (row_height - controller_->GetNameFontForRow(index).GetHeight()) / 2);
    235 
    236   bool is_rtl = controller_->IsRTL();
    237   int value_content_x = is_rtl ?
    238       entry_rect.width() - value_text_width - kEndPadding : kEndPadding;
    239 
    240   cairo_save(cairo_context);
    241   cairo_move_to(cairo_context, value_content_x, value_content_y);
    242   pango_cairo_show_layout(cairo_context, layout_);
    243   cairo_restore(cairo_context);
    244 
    245   // Use this to figure out where all the other Autofill items should be placed.
    246   int x_align_left = is_rtl ? kEndPadding : entry_rect.width() - kEndPadding;
    247 
    248   // Draw the Autofill icon, if one exists
    249   if (!controller_->icons()[index].empty()) {
    250     int icon = controller_->GetIconResourceID(controller_->icons()[index]);
    251     DCHECK_NE(-1, icon);
    252     int icon_y = entry_rect.y() + (row_height - kAutofillIconHeight) / 2;
    253 
    254     x_align_left += is_rtl ? 0 : -kAutofillIconWidth;
    255 
    256     cairo_save(cairo_context);
    257     ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
    258     gtk_util::DrawFullImage(cairo_context,
    259                             window_,
    260                             rb.GetImageNamed(icon),
    261                             x_align_left,
    262                             icon_y);
    263     cairo_restore(cairo_context);
    264 
    265     x_align_left += is_rtl ? kAutofillIconWidth + kIconPadding : -kIconPadding;
    266   }
    267 
    268   // Draw the subtext.
    269   SetLayoutText(controller_->subtexts()[index],
    270                 controller_->subtext_font(),
    271                 kSubtextColor);
    272   if (!is_rtl) {
    273     x_align_left -= controller_->subtext_font().GetStringWidth(
    274         controller_->subtexts()[index]);
    275   }
    276 
    277   // Center the text within the line.
    278   int subtext_content_y = std::max(
    279       entry_rect.y(),
    280       entry_rect.y() +
    281           (row_height - controller_->subtext_font().GetHeight()) / 2);
    282 
    283   cairo_save(cairo_context);
    284   cairo_move_to(cairo_context, x_align_left, subtext_content_y);
    285   pango_cairo_show_layout(cairo_context, layout_);
    286   cairo_restore(cairo_context);
    287 }
    288 
    289 AutofillPopupView* AutofillPopupView::Create(
    290     AutofillPopupController* controller) {
    291   return new AutofillPopupViewGtk(controller);
    292 }
    293 
    294 }  // namespace autofill
    295