| // Copyright (c) 2010 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/autocomplete/autocomplete_edit_view_gtk.h" | 
 |  | 
 | #include <gtk/gtk.h> | 
 | #include <gdk/gdkkeysyms.h> | 
 |  | 
 | #include <algorithm> | 
 |  | 
 | #include "app/clipboard/clipboard.h" | 
 | #include "app/clipboard/scoped_clipboard_writer.h" | 
 | #include "app/l10n_util.h" | 
 | #include "base/gtk_util.h" | 
 | #include "base/logging.h" | 
 | #include "base/utf_string_conversions.h" | 
 | #include "chrome/app/chrome_dll_resource.h" | 
 | #include "chrome/browser/autocomplete/autocomplete_edit.h" | 
 | #include "chrome/browser/autocomplete/autocomplete_popup_model.h" | 
 | #include "chrome/browser/bookmarks/bookmark_drag_data.h" | 
 | #include "chrome/browser/browser_process.h" | 
 | #include "chrome/browser/command_updater.h" | 
 | #include "chrome/browser/defaults.h" | 
 | #include "chrome/browser/gtk/gtk_util.h" | 
 | #include "chrome/browser/gtk/view_id_util.h" | 
 | #include "chrome/browser/tab_contents/tab_contents.h" | 
 | #include "chrome/browser/toolbar_model.h" | 
 | #include "chrome/common/notification_service.h" | 
 | #include "gfx/font.h" | 
 | #include "gfx/gtk_util.h" | 
 | #include "gfx/skia_utils_gtk.h" | 
 | #include "googleurl/src/gurl.h" | 
 | #include "grit/generated_resources.h" | 
 | #include "net/base/escape.h" | 
 |  | 
 | #if defined(TOOLKIT_VIEWS) | 
 | #include "chrome/browser/views/autocomplete/autocomplete_popup_contents_view.h" | 
 | #include "chrome/browser/views/location_bar/location_bar_view.h" | 
 | #else | 
 | #include "chrome/browser/autocomplete/autocomplete_popup_view_gtk.h" | 
 | #include "chrome/browser/gtk/gtk_theme_provider.h" | 
 | #include "chrome/browser/gtk/location_bar_view_gtk.h" | 
 | #endif | 
 |  | 
 | using gfx::SkColorToGdkColor; | 
 |  | 
 | namespace { | 
 |  | 
 | const gchar* kAutocompleteEditViewGtkKey = "__ACE_VIEW_GTK__"; | 
 |  | 
 | const char kTextBaseColor[] = "#808080"; | 
 | const char kSecureSchemeColor[] = "#079500"; | 
 | const char kSecurityErrorSchemeColor[] = "#a20000"; | 
 |  | 
 | const double kStrikethroughStrokeRed = 162.0 / 256.0; | 
 | const double kStrikethroughStrokeWidth = 2.0; | 
 |  | 
 | size_t GetUTF8Offset(const std::wstring& wide_text, size_t wide_text_offset) { | 
 |   return WideToUTF8(wide_text.substr(0, wide_text_offset)).size(); | 
 | } | 
 |  | 
 | // Stores GTK+-specific state so it can be restored after switching tabs. | 
 | struct ViewState { | 
 |   explicit ViewState(const AutocompleteEditViewGtk::CharRange& selection_range) | 
 |       : selection_range(selection_range) { | 
 |   } | 
 |  | 
 |   // Range of selected text. | 
 |   AutocompleteEditViewGtk::CharRange selection_range; | 
 | }; | 
 |  | 
 | struct AutocompleteEditState { | 
 |   AutocompleteEditState(const AutocompleteEditModel::State& model_state, | 
 |                         const ViewState& view_state) | 
 |       : model_state(model_state), | 
 |         view_state(view_state) { | 
 |   } | 
 |  | 
 |   const AutocompleteEditModel::State model_state; | 
 |   const ViewState view_state; | 
 | }; | 
 |  | 
 | // Returns a lazily initialized property bag accessor for saving our state in a | 
 | // TabContents. | 
 | PropertyAccessor<AutocompleteEditState>* GetStateAccessor() { | 
 |   static PropertyAccessor<AutocompleteEditState> state; | 
 |   return &state; | 
 | } | 
 |  | 
 | // Set up style properties to override the default GtkTextView; if a theme has | 
 | // overridden some of these properties, an inner-line will be displayed inside | 
 | // the fake GtkTextEntry. | 
 | void SetEntryStyle() { | 
 |   static bool style_was_set = false; | 
 |  | 
 |   if (style_was_set) | 
 |     return; | 
 |   style_was_set = true; | 
 |  | 
 |   gtk_rc_parse_string( | 
 |       "style \"chrome-location-bar-entry\" {" | 
 |       "  xthickness = 0\n" | 
 |       "  ythickness = 0\n" | 
 |       "  GtkWidget::focus_padding = 0\n" | 
 |       "  GtkWidget::focus-line-width = 0\n" | 
 |       "  GtkWidget::interior_focus = 0\n" | 
 |       "  GtkWidget::internal-padding = 0\n" | 
 |       "  GtkContainer::border-width = 0\n" | 
 |       "}\n" | 
 |       "widget \"*chrome-location-bar-entry\" " | 
 |       "style \"chrome-location-bar-entry\""); | 
 | } | 
 |  | 
 | // Copied from GTK+. Called when we lose the primary selection. This will clear | 
 | // the selection in the text buffer. | 
 | void ClipboardSelectionCleared(GtkClipboard* clipboard, | 
 |                                gpointer data) { | 
 |   GtkTextIter insert; | 
 |   GtkTextIter selection_bound; | 
 |   GtkTextBuffer* buffer = GTK_TEXT_BUFFER(data); | 
 |  | 
 |   gtk_text_buffer_get_iter_at_mark(buffer, &insert, | 
 |                                    gtk_text_buffer_get_insert(buffer)); | 
 |   gtk_text_buffer_get_iter_at_mark(buffer, &selection_bound, | 
 |                                    gtk_text_buffer_get_selection_bound(buffer)); | 
 |  | 
 |   if (!gtk_text_iter_equal(&insert, &selection_bound)) { | 
 |     gtk_text_buffer_move_mark(buffer, | 
 |                               gtk_text_buffer_get_selection_bound(buffer), | 
 |                               &insert); | 
 |   } | 
 | } | 
 |  | 
 | }  // namespace | 
 |  | 
 | AutocompleteEditViewGtk::AutocompleteEditViewGtk( | 
 |     AutocompleteEditController* controller, | 
 |     ToolbarModel* toolbar_model, | 
 |     Profile* profile, | 
 |     CommandUpdater* command_updater, | 
 |     bool popup_window_mode, | 
 | #if defined(TOOLKIT_VIEWS) | 
 |     const views::View* location_bar) | 
 | #else | 
 |     GtkWidget* location_bar) | 
 | #endif | 
 |     : text_view_(NULL), | 
 |       tag_table_(NULL), | 
 |       text_buffer_(NULL), | 
 |       faded_text_tag_(NULL), | 
 |       secure_scheme_tag_(NULL), | 
 |       security_error_scheme_tag_(NULL), | 
 |       model_(new AutocompleteEditModel(this, controller, profile)), | 
 | #if defined(TOOLKIT_VIEWS) | 
 |       popup_view_(new AutocompletePopupContentsView( | 
 |           gfx::Font(), this, model_.get(), profile, location_bar)), | 
 | #else | 
 |       popup_view_(new AutocompletePopupViewGtk(this, model_.get(), profile, | 
 |                                                location_bar)), | 
 | #endif | 
 |       controller_(controller), | 
 |       toolbar_model_(toolbar_model), | 
 |       command_updater_(command_updater), | 
 |       popup_window_mode_(popup_window_mode), | 
 |       security_level_(ToolbarModel::NONE), | 
 |       mark_set_handler_id_(0), | 
 | #if defined(OS_CHROMEOS) | 
 |       button_1_pressed_(false), | 
 |       text_selected_during_click_(false), | 
 |       text_view_focused_before_button_press_(false), | 
 | #endif | 
 | #if !defined(TOOLKIT_VIEWS) | 
 |       theme_provider_(GtkThemeProvider::GetFrom(profile)), | 
 | #endif | 
 |       enter_was_pressed_(false), | 
 |       tab_was_pressed_(false), | 
 |       paste_clipboard_requested_(false), | 
 |       enter_was_inserted_(false), | 
 |       enable_tab_to_search_(true) { | 
 |   model_->SetPopupModel(popup_view_->GetModel()); | 
 | } | 
 |  | 
 | AutocompleteEditViewGtk::~AutocompleteEditViewGtk() { | 
 |   NotificationService::current()->Notify( | 
 |       NotificationType::AUTOCOMPLETE_EDIT_DESTROYED, | 
 |       Source<AutocompleteEditViewGtk>(this), | 
 |       NotificationService::NoDetails()); | 
 |  | 
 |   // Explicitly teardown members which have a reference to us.  Just to be safe | 
 |   // we want them to be destroyed before destroying any other internal state. | 
 |   popup_view_.reset(); | 
 |   model_.reset(); | 
 |  | 
 |   // We own our widget and TextView related objects. | 
 |   if (alignment_.get()) {  // Init() has been called. | 
 |     alignment_.Destroy(); | 
 |     g_object_unref(text_buffer_); | 
 |     g_object_unref(tag_table_); | 
 |     // The tags we created are owned by the tag_table, and should be destroyed | 
 |     // along with it.  We don't hold our own reference to them. | 
 |   } | 
 | } | 
 |  | 
 | void AutocompleteEditViewGtk::Init() { | 
 |   SetEntryStyle(); | 
 |  | 
 |   // The height of the text view is going to change based on the font used.  We | 
 |   // don't want to stretch the height, and we want it vertically centered. | 
 |   alignment_.Own(gtk_alignment_new(0., 0.5, 1.0, 0.0)); | 
 |   gtk_widget_set_name(alignment_.get(), | 
 |                       "chrome-autocomplete-edit-view"); | 
 |  | 
 |   // The GtkTagTable and GtkTextBuffer are not initially unowned, so we have | 
 |   // our own reference when we create them, and we own them.  Adding them to | 
 |   // the other objects adds a reference; it doesn't adopt them. | 
 |   tag_table_ = gtk_text_tag_table_new(); | 
 |   text_buffer_ = gtk_text_buffer_new(tag_table_); | 
 |   g_object_set_data(G_OBJECT(text_buffer_), kAutocompleteEditViewGtkKey, this); | 
 |   text_view_ = gtk_text_view_new_with_buffer(text_buffer_); | 
 |   if (popup_window_mode_) | 
 |     gtk_text_view_set_editable(GTK_TEXT_VIEW(text_view_), false); | 
 |  | 
 |   // One pixel left margin is necessary to make the cursor visible when UI | 
 |   // language direction is LTR but |text_buffer_|'s content direction is RTL. | 
 |   gtk_text_view_set_left_margin(GTK_TEXT_VIEW(text_view_), 1); | 
 |  | 
 |   // See SetEntryStyle() comments. | 
 |   gtk_widget_set_name(text_view_, "chrome-location-bar-entry"); | 
 |  | 
 |   // The text view was floating.  It will now be owned by the alignment. | 
 |   gtk_container_add(GTK_CONTAINER(alignment_.get()), text_view_); | 
 |  | 
 |   // Do not allow inserting tab characters when pressing Tab key, so that when | 
 |   // Tab key is pressed, |text_view_| will emit "move-focus" signal, which will | 
 |   // be intercepted by our own handler to trigger Tab to search feature when | 
 |   // necessary. | 
 |   gtk_text_view_set_accepts_tab(GTK_TEXT_VIEW(text_view_), FALSE); | 
 |  | 
 |   faded_text_tag_ = gtk_text_buffer_create_tag(text_buffer_, | 
 |       NULL, "foreground", kTextBaseColor, NULL); | 
 |   secure_scheme_tag_ = gtk_text_buffer_create_tag(text_buffer_, | 
 |       NULL, "foreground", kSecureSchemeColor, NULL); | 
 |   security_error_scheme_tag_ = gtk_text_buffer_create_tag(text_buffer_, | 
 |       NULL, "foreground", kSecurityErrorSchemeColor, NULL); | 
 |   normal_text_tag_ = gtk_text_buffer_create_tag(text_buffer_, | 
 |       NULL, "foreground", "#000000", NULL); | 
 |  | 
 |   // NOTE: This code used to connect to "changed", however this was fired too | 
 |   // often and during bad times (our own buffer changes?).  It works out much | 
 |   // better to listen to end-user-action, which should be fired whenever the | 
 |   // user makes some sort of change to the buffer. | 
 |   g_signal_connect(text_buffer_, "begin-user-action", | 
 |                    G_CALLBACK(&HandleBeginUserActionThunk), this); | 
 |   g_signal_connect(text_buffer_, "end-user-action", | 
 |                    G_CALLBACK(&HandleEndUserActionThunk), this); | 
 |   g_signal_connect(text_buffer_, "insert-text", | 
 |                    G_CALLBACK(&HandleInsertTextThunk), this); | 
 |   // We connect to key press and release for special handling of a few keys. | 
 |   g_signal_connect(text_view_, "key-press-event", | 
 |                    G_CALLBACK(&HandleKeyPressThunk), this); | 
 |   g_signal_connect(text_view_, "key-release-event", | 
 |                    G_CALLBACK(&HandleKeyReleaseThunk), this); | 
 |   g_signal_connect(text_view_, "button-press-event", | 
 |                    G_CALLBACK(&HandleViewButtonPressThunk), this); | 
 |   g_signal_connect(text_view_, "button-release-event", | 
 |                    G_CALLBACK(&HandleViewButtonReleaseThunk), this); | 
 |   g_signal_connect(text_view_, "focus-in-event", | 
 |                    G_CALLBACK(&HandleViewFocusInThunk), this); | 
 |   g_signal_connect(text_view_, "focus-out-event", | 
 |                    G_CALLBACK(&HandleViewFocusOutThunk), this); | 
 |   // NOTE: The GtkTextView documentation asks you not to connect to this | 
 |   // signal, but it is very convenient and clean for catching up/down. | 
 |   g_signal_connect(text_view_, "move-cursor", | 
 |                    G_CALLBACK(&HandleViewMoveCursorThunk), this); | 
 |   g_signal_connect(text_view_, "move-focus", | 
 |                    G_CALLBACK(&HandleViewMoveFocusThunk), this); | 
 |   // Override the size request.  We want to keep the original height request | 
 |   // from the widget, since that's font dependent.  We want to ignore the width | 
 |   // so we don't force a minimum width based on the text length. | 
 |   g_signal_connect(text_view_, "size-request", | 
 |                    G_CALLBACK(&HandleViewSizeRequestThunk), this); | 
 |   g_signal_connect(text_view_, "populate-popup", | 
 |                    G_CALLBACK(&HandlePopulatePopupThunk), this); | 
 |   mark_set_handler_id_ = g_signal_connect( | 
 |       text_buffer_, "mark-set", G_CALLBACK(&HandleMarkSetThunk), this); | 
 |   mark_set_handler_id2_ = g_signal_connect_after( | 
 |       text_buffer_, "mark-set", G_CALLBACK(&HandleMarkSetAfterThunk), this); | 
 |   g_signal_connect(text_view_, "drag-data-received", | 
 |                    G_CALLBACK(&HandleDragDataReceivedThunk), this); | 
 |   g_signal_connect(text_view_, "backspace", | 
 |                    G_CALLBACK(&HandleBackSpaceThunk), this); | 
 |   g_signal_connect(text_view_, "copy-clipboard", | 
 |                    G_CALLBACK(&HandleCopyClipboardThunk), this); | 
 |   g_signal_connect(text_view_, "cut-clipboard", | 
 |                    G_CALLBACK(&HandleCutClipboardThunk), this); | 
 |   g_signal_connect(text_view_, "paste-clipboard", | 
 |                    G_CALLBACK(&HandlePasteClipboardThunk), this); | 
 |   g_signal_connect_after(text_view_, "expose-event", | 
 |                          G_CALLBACK(&HandleExposeEventThunk), this); | 
 |   g_signal_connect(text_view_, "direction-changed", | 
 |                    G_CALLBACK(&HandleWidgetDirectionChangedThunk), this); | 
 |  | 
 | #if !defined(TOOLKIT_VIEWS) | 
 |   registrar_.Add(this, | 
 |                  NotificationType::BROWSER_THEME_CHANGED, | 
 |                  NotificationService::AllSources()); | 
 |   theme_provider_->InitThemesFor(this); | 
 | #else | 
 |   // Manually invoke SetBaseColor() because TOOLKIT_VIEWS doesn't observe | 
 |   // themes. | 
 |   SetBaseColor(); | 
 | #endif | 
 |  | 
 |   ViewIDUtil::SetID(GetNativeView(), VIEW_ID_AUTOCOMPLETE); | 
 | } | 
 |  | 
 | void AutocompleteEditViewGtk::SetFocus() { | 
 |   gtk_widget_grab_focus(text_view_); | 
 | } | 
 |  | 
 | int AutocompleteEditViewGtk::TextWidth() { | 
 |   int horizontal_border_size = | 
 |       gtk_text_view_get_border_window_size(GTK_TEXT_VIEW(text_view_), | 
 |                                            GTK_TEXT_WINDOW_LEFT) + | 
 |       gtk_text_view_get_border_window_size(GTK_TEXT_VIEW(text_view_), | 
 |                                            GTK_TEXT_WINDOW_RIGHT) + | 
 |       gtk_text_view_get_left_margin(GTK_TEXT_VIEW(text_view_)) + | 
 |       gtk_text_view_get_right_margin(GTK_TEXT_VIEW(text_view_)); | 
 |  | 
 |   GtkTextIter start, end; | 
 |   GdkRectangle first_char_bounds, last_char_bounds; | 
 |   gtk_text_buffer_get_start_iter(text_buffer_, &start); | 
 |   gtk_text_buffer_get_end_iter(text_buffer_, &end); | 
 |   gtk_text_view_get_iter_location(GTK_TEXT_VIEW(text_view_), | 
 |                                   &start, &first_char_bounds); | 
 |   gtk_text_view_get_iter_location(GTK_TEXT_VIEW(text_view_), | 
 |                                   &end, &last_char_bounds); | 
 |   return ((last_char_bounds.x > first_char_bounds.x) ? | 
 |           (last_char_bounds.x + last_char_bounds.width - first_char_bounds.x) : | 
 |           (first_char_bounds.x - last_char_bounds.x + last_char_bounds.width)) + | 
 |       horizontal_border_size; | 
 | } | 
 |  | 
 | void AutocompleteEditViewGtk::SaveStateToTab(TabContents* tab) { | 
 |   DCHECK(tab); | 
 |   // If any text has been selected, register it as the PRIMARY selection so it | 
 |   // can still be pasted via middle-click after the text view is cleared. | 
 |   if (!selected_text_.empty()) | 
 |     SavePrimarySelection(selected_text_); | 
 |   // NOTE: GetStateForTabSwitch may affect GetSelection, so order is important. | 
 |   AutocompleteEditModel::State model_state = model_->GetStateForTabSwitch(); | 
 |   GetStateAccessor()->SetProperty( | 
 |       tab->property_bag(), | 
 |       AutocompleteEditState(model_state, ViewState(GetSelection()))); | 
 | } | 
 |  | 
 | void AutocompleteEditViewGtk::Update(const TabContents* contents) { | 
 |   // NOTE: We're getting the URL text here from the ToolbarModel. | 
 |   bool visibly_changed_permanent_text = | 
 |       model_->UpdatePermanentText(toolbar_model_->GetText()); | 
 |  | 
 |   ToolbarModel::SecurityLevel security_level = | 
 |         toolbar_model_->GetSecurityLevel(); | 
 |   bool changed_security_level = (security_level != security_level_); | 
 |   security_level_ = security_level; | 
 |  | 
 |   if (contents) { | 
 |     selected_text_.clear(); | 
 |     RevertAll(); | 
 |     const AutocompleteEditState* state = | 
 |         GetStateAccessor()->GetProperty(contents->property_bag()); | 
 |     if (state) { | 
 |       model_->RestoreState(state->model_state); | 
 |  | 
 |       // Move the marks for the cursor and the other end of the selection to | 
 |       // the previously-saved offsets (but preserve PRIMARY). | 
 |       StartUpdatingHighlightedText(); | 
 |       SetSelectedRange(state->view_state.selection_range); | 
 |       FinishUpdatingHighlightedText(); | 
 |     } | 
 |   } else if (visibly_changed_permanent_text) { | 
 |     RevertAll(); | 
 |     // TODO(deanm): There should be code to restore select all here. | 
 |   } else if (changed_security_level) { | 
 |     EmphasizeURLComponents(); | 
 |   } | 
 | } | 
 |  | 
 | void AutocompleteEditViewGtk::OpenURL(const GURL& url, | 
 |                                       WindowOpenDisposition disposition, | 
 |                                       PageTransition::Type transition, | 
 |                                       const GURL& alternate_nav_url, | 
 |                                       size_t selected_line, | 
 |                                       const std::wstring& keyword) { | 
 |   if (!url.is_valid()) | 
 |     return; | 
 |  | 
 |   model_->OpenURL(url, disposition, transition, alternate_nav_url, | 
 |                   selected_line, keyword); | 
 | } | 
 |  | 
 | std::wstring AutocompleteEditViewGtk::GetText() const { | 
 |   GtkTextIter start, end; | 
 |   gtk_text_buffer_get_bounds(text_buffer_, &start, &end); | 
 |   gchar* utf8 = gtk_text_buffer_get_text(text_buffer_, &start, &end, false); | 
 |   std::wstring out(UTF8ToWide(utf8)); | 
 |   g_free(utf8); | 
 |   return out; | 
 | } | 
 |  | 
 | bool AutocompleteEditViewGtk::IsEditingOrEmpty() const { | 
 |   return model_->user_input_in_progress() || | 
 |       (gtk_text_buffer_get_char_count(text_buffer_) == 0); | 
 | } | 
 |  | 
 | int AutocompleteEditViewGtk::GetIcon() const { | 
 |   return IsEditingOrEmpty() ? | 
 |       AutocompleteMatch::TypeToIcon(model_->CurrentTextType()) : | 
 |       toolbar_model_->GetIcon(); | 
 | } | 
 |  | 
 | void AutocompleteEditViewGtk::SetUserText(const std::wstring& text, | 
 |                                           const std::wstring& display_text, | 
 |                                           bool update_popup) { | 
 |   model_->SetUserText(text); | 
 |   // TODO(deanm): something about selection / focus change here. | 
 |   SetWindowTextAndCaretPos(display_text, display_text.length()); | 
 |   if (update_popup) | 
 |     UpdatePopup(); | 
 |   TextChanged(); | 
 | } | 
 |  | 
 | void AutocompleteEditViewGtk::SetWindowTextAndCaretPos(const std::wstring& text, | 
 |                                                        size_t caret_pos) { | 
 |   CharRange range(static_cast<int>(caret_pos), static_cast<int>(caret_pos)); | 
 |   SetTextAndSelectedRange(text, range); | 
 | } | 
 |  | 
 | void AutocompleteEditViewGtk::SetForcedQuery() { | 
 |   const std::wstring current_text(GetText()); | 
 |   if (current_text.empty() || (current_text[0] != '?')) { | 
 |     SetUserText(L"?"); | 
 |   } else { | 
 |     StartUpdatingHighlightedText(); | 
 |     SetSelectedRange(CharRange(current_text.size(), 1)); | 
 |     FinishUpdatingHighlightedText(); | 
 |   } | 
 | } | 
 |  | 
 | bool AutocompleteEditViewGtk::IsSelectAll() { | 
 |   GtkTextIter sel_start, sel_end; | 
 |   gtk_text_buffer_get_selection_bounds(text_buffer_, &sel_start, &sel_end); | 
 |  | 
 |   GtkTextIter start, end; | 
 |   gtk_text_buffer_get_bounds(text_buffer_, &start, &end); | 
 |  | 
 |   // Returns true if the |text_buffer_| is empty. | 
 |   return gtk_text_iter_equal(&start, &sel_start) && | 
 |       gtk_text_iter_equal(&end, &sel_end); | 
 | } | 
 |  | 
 | void AutocompleteEditViewGtk::SelectAll(bool reversed) { | 
 |   // SelectAll() is invoked as a side effect of other actions (e.g.  switching | 
 |   // tabs or hitting Escape) in autocomplete_edit.cc, so we don't update the | 
 |   // PRIMARY selection here. | 
 |   SelectAllInternal(reversed, false); | 
 | } | 
 |  | 
 | void AutocompleteEditViewGtk::RevertAll() { | 
 |   ClosePopup(); | 
 |   model_->Revert(); | 
 |   TextChanged(); | 
 | } | 
 |  | 
 | void AutocompleteEditViewGtk::UpdatePopup() { | 
 |   model_->SetInputInProgress(true); | 
 |   if (!model_->has_focus()) | 
 |     return; | 
 |  | 
 |   // Don't inline autocomplete when the caret/selection isn't at the end of | 
 |   // the text. | 
 |   CharRange sel = GetSelection(); | 
 |   model_->StartAutocomplete(std::max(sel.cp_max, sel.cp_min) < GetTextLength()); | 
 | } | 
 |  | 
 | void AutocompleteEditViewGtk::ClosePopup() { | 
 |   popup_view_->GetModel()->StopAutocomplete(); | 
 | } | 
 |  | 
 | void AutocompleteEditViewGtk::OnTemporaryTextMaybeChanged( | 
 |     const std::wstring& display_text, | 
 |     bool save_original_selection) { | 
 |   if (save_original_selection) | 
 |     saved_temporary_selection_ = GetSelection(); | 
 |  | 
 |   StartUpdatingHighlightedText(); | 
 |   SetWindowTextAndCaretPos(display_text, display_text.length()); | 
 |   FinishUpdatingHighlightedText(); | 
 |   TextChanged(); | 
 | } | 
 |  | 
 | bool AutocompleteEditViewGtk::OnInlineAutocompleteTextMaybeChanged( | 
 |     const std::wstring& display_text, | 
 |     size_t user_text_length) { | 
 |   if (display_text == GetText()) | 
 |     return false; | 
 |  | 
 |   StartUpdatingHighlightedText(); | 
 |   CharRange range(display_text.size(), user_text_length); | 
 |   SetTextAndSelectedRange(display_text, range); | 
 |   FinishUpdatingHighlightedText(); | 
 |   TextChanged(); | 
 |   return true; | 
 | } | 
 |  | 
 | void AutocompleteEditViewGtk::OnRevertTemporaryText() { | 
 |   StartUpdatingHighlightedText(); | 
 |   SetSelectedRange(saved_temporary_selection_); | 
 |   FinishUpdatingHighlightedText(); | 
 |   TextChanged(); | 
 | } | 
 |  | 
 | void AutocompleteEditViewGtk::OnBeforePossibleChange() { | 
 |   // If this change is caused by a paste clipboard action and all text is | 
 |   // selected, then call model_->on_paste_replacing_all() to prevent inline | 
 |   // autocomplete. | 
 |   if (paste_clipboard_requested_) { | 
 |     paste_clipboard_requested_ = false; | 
 |     if (IsSelectAll()) | 
 |       model_->on_paste_replacing_all(); | 
 |   } | 
 |  | 
 |   // Record our state. | 
 |   text_before_change_ = GetText(); | 
 |   sel_before_change_ = GetSelection(); | 
 | } | 
 |  | 
 | // TODO(deanm): This is mostly stolen from Windows, and will need some work. | 
 | bool AutocompleteEditViewGtk::OnAfterPossibleChange() { | 
 |   // If the change is caused by an Enter key press event, and the event was not | 
 |   // handled by IME, then it's an unexpected change and shall be reverted here. | 
 |   // {Start|Finish}UpdatingHighlightedText() are called here to prevent the | 
 |   // PRIMARY selection from being changed. | 
 |   if (enter_was_pressed_ && enter_was_inserted_) { | 
 |     StartUpdatingHighlightedText(); | 
 |     SetTextAndSelectedRange(text_before_change_, sel_before_change_); | 
 |     FinishUpdatingHighlightedText(); | 
 |     return false; | 
 |   } | 
 |  | 
 |   CharRange new_sel = GetSelection(); | 
 |   int length = GetTextLength(); | 
 |   bool selection_differs = (new_sel.cp_min != sel_before_change_.cp_min) || | 
 |                            (new_sel.cp_max != sel_before_change_.cp_max); | 
 |   bool at_end_of_edit = (new_sel.cp_min == length && new_sel.cp_max == length); | 
 |  | 
 |   // See if the text or selection have changed since OnBeforePossibleChange(). | 
 |   std::wstring new_text(GetText()); | 
 |   text_changed_ = (new_text != text_before_change_); | 
 |  | 
 |   if (text_changed_) | 
 |     AdjustTextJustification(); | 
 |  | 
 |   // When the user has deleted text, we don't allow inline autocomplete.  Make | 
 |   // sure to not flag cases like selecting part of the text and then pasting | 
 |   // (or typing) the prefix of that selection.  (We detect these by making | 
 |   // sure the caret, which should be after any insertion, hasn't moved | 
 |   // forward of the old selection start.) | 
 |   bool just_deleted_text = | 
 |       (text_before_change_.length() > new_text.length()) && | 
 |       (new_sel.cp_min <= std::min(sel_before_change_.cp_min, | 
 |                                  sel_before_change_.cp_max)); | 
 |  | 
 |   bool something_changed = model_->OnAfterPossibleChange(new_text, | 
 |       selection_differs, text_changed_, just_deleted_text, at_end_of_edit); | 
 |  | 
 |   // If only selection was changed, we don't need to call |controller_|'s | 
 |   // OnChanged() method, which is called in TextChanged(). | 
 |   // But we still need to call EmphasizeURLComponents() to make sure the text | 
 |   // attributes are updated correctly. | 
 |   if (something_changed && text_changed_) | 
 |     TextChanged(); | 
 |   else if (selection_differs) | 
 |     EmphasizeURLComponents(); | 
 |  | 
 |   return something_changed; | 
 | } | 
 |  | 
 | gfx::NativeView AutocompleteEditViewGtk::GetNativeView() const { | 
 |   return alignment_.get(); | 
 | } | 
 |  | 
 | CommandUpdater* AutocompleteEditViewGtk::GetCommandUpdater() { | 
 |   return command_updater_; | 
 | } | 
 |  | 
 | void AutocompleteEditViewGtk::Observe(NotificationType type, | 
 |                                       const NotificationSource& source, | 
 |                                       const NotificationDetails& details) { | 
 |   DCHECK(type == NotificationType::BROWSER_THEME_CHANGED); | 
 |  | 
 |   SetBaseColor(); | 
 | } | 
 |  | 
 | void AutocompleteEditViewGtk::SetBaseColor() { | 
 | #if defined(TOOLKIT_VIEWS) | 
 |   bool use_gtk = false; | 
 | #else | 
 |   bool use_gtk = theme_provider_->UseGtkTheme(); | 
 | #endif | 
 |  | 
 |   if (use_gtk) { | 
 |     gtk_widget_modify_cursor(text_view_, NULL, NULL); | 
 |     gtk_widget_modify_base(text_view_, GTK_STATE_NORMAL, NULL); | 
 |     gtk_widget_modify_base(text_view_, GTK_STATE_SELECTED, NULL); | 
 |     gtk_widget_modify_text(text_view_, GTK_STATE_SELECTED, NULL); | 
 |     gtk_widget_modify_base(text_view_, GTK_STATE_ACTIVE, NULL); | 
 |     gtk_widget_modify_text(text_view_, GTK_STATE_ACTIVE, NULL); | 
 |  | 
 |     gtk_util::UndoForceFontSize(text_view_); | 
 |  | 
 |     // Grab the text colors out of the style and set our tags to use them. | 
 |     GtkStyle* style = gtk_rc_get_style(text_view_); | 
 |  | 
 |     // style may be unrealized at this point, so calculate the halfway point | 
 |     // between text[] and base[] manually instead of just using text_aa[]. | 
 |     GdkColor average_color = gtk_util::AverageColors( | 
 |         style->text[GTK_STATE_NORMAL], style->base[GTK_STATE_NORMAL]); | 
 |  | 
 |     g_object_set(faded_text_tag_, "foreground-gdk", &average_color, NULL); | 
 |     g_object_set(normal_text_tag_, "foreground-gdk", | 
 |                  &style->text[GTK_STATE_NORMAL], NULL); | 
 |   } else { | 
 |     const GdkColor* background_color_ptr; | 
 | #if defined(TOOLKIT_VIEWS) | 
 |     const GdkColor background_color = gfx::SkColorToGdkColor( | 
 |         LocationBarView::GetColor(ToolbarModel::NONE, | 
 |                                   LocationBarView::BACKGROUND)); | 
 |     background_color_ptr = &background_color; | 
 | #else | 
 |     background_color_ptr = &LocationBarViewGtk::kBackgroundColor; | 
 | #endif | 
 |     gtk_widget_modify_cursor(text_view_, &gfx::kGdkBlack, &gfx::kGdkGray); | 
 |     gtk_widget_modify_base(text_view_, GTK_STATE_NORMAL, background_color_ptr); | 
 |  | 
 | #if !defined(TOOLKIT_VIEWS) | 
 |     // Override the selected colors so we don't leak colors from the current | 
 |     // gtk theme into the chrome-theme. | 
 |     GdkColor c; | 
 |     c = SkColorToGdkColor(theme_provider_->get_active_selection_bg_color()); | 
 |     gtk_widget_modify_base(text_view_, GTK_STATE_SELECTED, &c); | 
 |  | 
 |     c = SkColorToGdkColor(theme_provider_->get_active_selection_fg_color()); | 
 |     gtk_widget_modify_text(text_view_, GTK_STATE_SELECTED, &c); | 
 |  | 
 |     c = SkColorToGdkColor(theme_provider_->get_inactive_selection_bg_color()); | 
 |     gtk_widget_modify_base(text_view_, GTK_STATE_ACTIVE, &c); | 
 |  | 
 |     c = SkColorToGdkColor(theme_provider_->get_inactive_selection_fg_color()); | 
 |     gtk_widget_modify_text(text_view_, GTK_STATE_ACTIVE, &c); | 
 | #endif | 
 |  | 
 |     // Until we switch to vector graphics, force the font size. | 
 |     gtk_util::ForceFontSizePixels(text_view_, | 
 |         popup_window_mode_ ? | 
 |         browser_defaults::kAutocompleteEditFontPixelSizeInPopup : | 
 |         browser_defaults::kAutocompleteEditFontPixelSize); | 
 |  | 
 |     g_object_set(faded_text_tag_, "foreground", kTextBaseColor, NULL); | 
 |     g_object_set(normal_text_tag_, "foreground", "#000000", NULL); | 
 |   } | 
 | } | 
 |  | 
 | void AutocompleteEditViewGtk::HandleBeginUserAction(GtkTextBuffer* sender) { | 
 |   OnBeforePossibleChange(); | 
 | } | 
 |  | 
 | void AutocompleteEditViewGtk::HandleEndUserAction(GtkTextBuffer* sender) { | 
 |   OnAfterPossibleChange(); | 
 | } | 
 |  | 
 | gboolean AutocompleteEditViewGtk::HandleKeyPress(GtkWidget* widget, | 
 |                                                  GdkEventKey* event) { | 
 |   // Background of this piece of complicated code: | 
 |   // The omnibox supports several special behaviors which may be triggered by | 
 |   // certain key events: | 
 |   // Tab to search - triggered by Tab key | 
 |   // Accept input - triggered by Enter key | 
 |   // Revert input - triggered by Escape key | 
 |   // | 
 |   // Because we use a GtkTextView object |text_view_| for text input, we need | 
 |   // send all key events to |text_view_| before handling them, to make sure | 
 |   // IME works without any problem. So here, we intercept "key-press-event" | 
 |   // signal of |text_view_| object and call its default handler to handle the | 
 |   // key event first. | 
 |   // | 
 |   // Then if the key event is one of Tab, Enter and Escape, we need to trigger | 
 |   // the corresponding special behavior if IME did not handle it. | 
 |   // For Escape key, if the default signal handler returns FALSE, then we know | 
 |   // it's not handled by IME. | 
 |   // | 
 |   // For Tab key, as "accepts-tab" property of |text_view_| is set to FALSE, | 
 |   // if IME did not handle it then "move-focus" signal will be emitted by the | 
 |   // default signal handler of |text_view_|. So we can intercept "move-focus" | 
 |   // signal of |text_view_| to know if a Tab key press event was handled by IME, | 
 |   // and trigger Tab to search behavior when necessary in the signal handler. | 
 |   // | 
 |   // But for Enter key, if IME did not handle the key event, the default signal | 
 |   // handler will delete current selection range and insert '\n' and always | 
 |   // return TRUE. We need to prevent |text_view_| from performing this default | 
 |   // action if IME did not handle the key event, because we don't want the | 
 |   // content of omnibox to be changed before triggering our special behavior. | 
 |   // Otherwise our special behavior would not be performed correctly. | 
 |   // | 
 |   // But there is no way for us to prevent GtkTextView from handling the key | 
 |   // event and performing built-in operation. So in order to achieve our goal, | 
 |   // "insert-text" signal of |text_buffer_| object is intercepted, and | 
 |   // following actions are done in the signal handler: | 
 |   // - If there is only one character in inserted text, and it's '\n' or '\r', | 
 |   //   then set |enter_was_inserted_| to true. | 
 |   // - Filter out all new line and tab characters. | 
 |   // | 
 |   // So if |enter_was_inserted_| is true after calling |text_view_|'s default | 
 |   // signal handler against an Enter key press event, then we know that the | 
 |   // Enter key press event was handled by GtkTextView rather than IME, and can | 
 |   // perform the special behavior for Enter key safely. | 
 |   // | 
 |   // Now the last thing is to prevent the content of omnibox from being changed | 
 |   // by GtkTextView when Enter key is pressed. As OnBeforePossibleChange() and | 
 |   // OnAfterPossibleChange() will be called by GtkTextView before and after | 
 |   // changing the content, and the content is already saved in | 
 |   // OnBeforePossibleChange(), so if the Enter key press event was not handled | 
 |   // by IME, it's easy to restore the content in OnAfterPossibleChange(), as if | 
 |   // it's not changed at all. | 
 |  | 
 |   GtkWidgetClass* klass = GTK_WIDGET_GET_CLASS(widget); | 
 |  | 
 |   enter_was_pressed_ = (event->keyval == GDK_Return || | 
 |                         event->keyval == GDK_ISO_Enter || | 
 |                         event->keyval == GDK_KP_Enter); | 
 |  | 
 |   // Set |tab_was_pressed_| to true if it's a Tab key press event, so that our | 
 |   // handler of "move-focus" signal can trigger Tab to search behavior when | 
 |   // necessary. | 
 |   tab_was_pressed_ = ((event->keyval == GDK_Tab || | 
 |                        event->keyval == GDK_ISO_Left_Tab || | 
 |                        event->keyval == GDK_KP_Tab) && | 
 |                       !(event->state & GDK_CONTROL_MASK)); | 
 |  | 
 |   // Reset |enter_was_inserted_|, which may be set in the "insert-text" signal | 
 |   // handler, so that we'll know if an Enter key event was handled by IME. | 
 |   enter_was_inserted_ = false; | 
 |  | 
 |   // Reset |paste_clipboard_requested_| to make sure we won't misinterpret this | 
 |   // key input action as a paste action. | 
 |   paste_clipboard_requested_ = false; | 
 |  | 
 |   // Reset |text_changed_| before passing the key event on to the text view. | 
 |   text_changed_ = false; | 
 |  | 
 |   // Call the default handler, so that IME can work as normal. | 
 |   // New line characters will be filtered out by our "insert-text" | 
 |   // signal handler attached to |text_buffer_| object. | 
 |   gboolean result = klass->key_press_event(widget, event); | 
 |  | 
 |   // Set |tab_was_pressed_| to false, to make sure Tab to search behavior can | 
 |   // only be triggered by pressing Tab key. | 
 |   tab_was_pressed_ = false; | 
 |  | 
 |   if (enter_was_pressed_ && enter_was_inserted_) { | 
 |     bool alt_held = (event->state & GDK_MOD1_MASK); | 
 |     model_->AcceptInput(alt_held ? NEW_FOREGROUND_TAB : CURRENT_TAB, false); | 
 |     result = TRUE; | 
 |   } else if (!result && event->keyval == GDK_Escape && | 
 |              (event->state & gtk_accelerator_get_default_mod_mask()) == 0) { | 
 |     // We can handle the Escape key if |text_view_| did not handle it. | 
 |     // If it's not handled by us, then we need to propagate it up to the parent | 
 |     // widgets, so that Escape accelerator can still work. | 
 |     result = model_->OnEscapeKeyPressed(); | 
 |   } else if (event->keyval == GDK_Control_L || event->keyval == GDK_Control_R) { | 
 |     // Omnibox2 can switch its contents while pressing a control key. To switch | 
 |     // the contents of omnibox2, we notify the AutocompleteEditModel class when | 
 |     // the control-key state is changed. | 
 |     model_->OnControlKeyChanged(true); | 
 |   } else if (!text_changed_ && event->keyval == GDK_Delete && | 
 |              event->state & GDK_SHIFT_MASK) { | 
 |     // If shift+del didn't change the text, we let this delete an entry from | 
 |     // the popup.  We can't check to see if the IME handled it because even if | 
 |     // nothing is selected, the IME or the TextView still report handling it. | 
 |     AutocompletePopupModel* popup_model = popup_view_->GetModel(); | 
 |     if (popup_model->IsOpen()) | 
 |       popup_model->TryDeletingCurrentItem(); | 
 |   } | 
 |  | 
 |   // Set |enter_was_pressed_| to false, to make sure OnAfterPossibleChange() can | 
 |   // act as normal for changes made by other events. | 
 |   enter_was_pressed_ = false; | 
 |  | 
 |   // If the key event is not handled by |text_view_| or us, then we need to | 
 |   // propagate the key event up to parent widgets by returning FALSE. | 
 |   // In this case we need to stop the signal emission explicitly to prevent the | 
 |   // default "key-press-event" handler of |text_view_| from being called again. | 
 |   if (!result) { | 
 |     static guint signal_id = | 
 |         g_signal_lookup("key-press-event", GTK_TYPE_WIDGET); | 
 |     g_signal_stop_emission(widget, signal_id, 0); | 
 |   } | 
 |  | 
 |   return result; | 
 | } | 
 |  | 
 | gboolean AutocompleteEditViewGtk::HandleKeyRelease(GtkWidget* widget, | 
 |                                                    GdkEventKey* event) { | 
 |   // Omnibox2 can switch its contents while pressing a control key. To switch | 
 |   // the contents of omnibox2, we notify the AutocompleteEditModel class when | 
 |   // the control-key state is changed. | 
 |   if (event->keyval == GDK_Control_L || event->keyval == GDK_Control_R) { | 
 |     // Round trip to query the control state after the release.  This allows | 
 |     // you to release one control key while still holding another control key. | 
 |     GdkDisplay* display = gdk_drawable_get_display(event->window); | 
 |     GdkModifierType mod; | 
 |     gdk_display_get_pointer(display, NULL, NULL, NULL, &mod); | 
 |     if (!(mod & GDK_CONTROL_MASK)) | 
 |       model_->OnControlKeyChanged(false); | 
 |   } | 
 |  | 
 |   // Even though we handled the press ourselves, let GtkTextView handle the | 
 |   // release.  It shouldn't do anything particularly interesting, but it will | 
 |   // handle the IME work for us. | 
 |   return FALSE;  // Propagate into GtkTextView. | 
 | } | 
 |  | 
 | gboolean AutocompleteEditViewGtk::HandleViewButtonPress(GtkWidget* sender, | 
 |                                                         GdkEventButton* event) { | 
 |   // We don't need to care about double and triple clicks. | 
 |   if (event->type != GDK_BUTTON_PRESS) | 
 |     return FALSE; | 
 |  | 
 |   if (event->button == 1) { | 
 | #if defined(OS_CHROMEOS) | 
 |     // When the first button is pressed, track some stuff that will help us | 
 |     // determine whether we should select all of the text when the button is | 
 |     // released. | 
 |     button_1_pressed_ = true; | 
 |     text_view_focused_before_button_press_ = GTK_WIDGET_HAS_FOCUS(text_view_); | 
 |     text_selected_during_click_ = false; | 
 | #endif | 
 |  | 
 |     // Button press event may change the selection, we need to record the change | 
 |     // and report it to |model_| later when button is released. | 
 |     OnBeforePossibleChange(); | 
 |   } else if (event->button == 2) { | 
 |     // GtkTextView pastes PRIMARY selection with middle click. | 
 |     // We can't call model_->on_paste_replacing_all() here, because the actual | 
 |     // paste clipboard action may not be performed if the clipboard is empty. | 
 |     paste_clipboard_requested_ = true; | 
 |   } | 
 |   return FALSE; | 
 | } | 
 |  | 
 | gboolean AutocompleteEditViewGtk::HandleViewButtonRelease( | 
 |     GtkWidget* sender, GdkEventButton* event) { | 
 |   if (event->button != 1) | 
 |     return FALSE; | 
 |  | 
 | #if defined(OS_CHROMEOS) | 
 |   button_1_pressed_ = false; | 
 | #endif | 
 |  | 
 |   // Call the GtkTextView default handler, ignoring the fact that it will | 
 |   // likely have told us to stop propagating.  We want to handle selection. | 
 |   GtkWidgetClass* klass = GTK_WIDGET_GET_CLASS(text_view_); | 
 |   klass->button_release_event(text_view_, event); | 
 |  | 
 | #if defined(OS_CHROMEOS) | 
 |   if (!text_view_focused_before_button_press_ && !text_selected_during_click_) { | 
 |     // If this was a focusing click and the user didn't drag to highlight any | 
 |     // text, select the full input and update the PRIMARY selection. | 
 |     SelectAllInternal(false, true); | 
 |  | 
 |     // So we told the buffer where the cursor should be, but make sure to tell | 
 |     // the view so it can scroll it to be visible if needed. | 
 |     // NOTE: This function doesn't seem to like a count of 0, looking at the | 
 |     // code it will skip an important loop.  Use -1 to achieve the same. | 
 |     GtkTextIter start, end; | 
 |     gtk_text_buffer_get_bounds(text_buffer_, &start, &end); | 
 |     gtk_text_view_move_visually(GTK_TEXT_VIEW(text_view_), &start, -1); | 
 |   } | 
 | #endif | 
 |  | 
 |   // Inform |model_| about possible text selection change. | 
 |   OnAfterPossibleChange(); | 
 |  | 
 |   return TRUE;  // Don't continue, we called the default handler already. | 
 | } | 
 |  | 
 | gboolean AutocompleteEditViewGtk::HandleViewFocusIn(GtkWidget* sender, | 
 |                                                     GdkEventFocus* event) { | 
 |   GdkModifierType modifiers; | 
 |   gdk_window_get_pointer(text_view_->window, NULL, NULL, &modifiers); | 
 |   model_->OnSetFocus((modifiers & GDK_CONTROL_MASK) != 0); | 
 |   controller_->OnSetFocus(); | 
 |   // TODO(deanm): Some keyword hit business, etc here. | 
 |  | 
 |   g_signal_connect( | 
 |       gdk_keymap_get_for_display(gtk_widget_get_display(text_view_)), | 
 |       "direction-changed", | 
 |       G_CALLBACK(&HandleKeymapDirectionChangedThunk), this); | 
 |  | 
 |   AdjustTextJustification(); | 
 |  | 
 |   return FALSE;  // Continue propagation. | 
 | } | 
 |  | 
 | gboolean AutocompleteEditViewGtk::HandleViewFocusOut(GtkWidget* sender, | 
 |                                                      GdkEventFocus* event) { | 
 |   // Close the popup. | 
 |   ClosePopup(); | 
 |   // Tell the model to reset itself. | 
 |   model_->OnKillFocus(); | 
 |   controller_->OnKillFocus(); | 
 |  | 
 |   g_signal_handlers_disconnect_by_func( | 
 |       gdk_keymap_get_for_display(gtk_widget_get_display(text_view_)), | 
 |       reinterpret_cast<gpointer>(&HandleKeymapDirectionChangedThunk), this); | 
 |  | 
 |   return FALSE;  // Pass the event on to the GtkTextView. | 
 | } | 
 |  | 
 | void AutocompleteEditViewGtk::HandleViewMoveCursor( | 
 |     GtkWidget* sender, | 
 |     GtkMovementStep step, | 
 |     gint count, | 
 |     gboolean extend_selection) { | 
 |   GtkTextIter sel_start, sel_end; | 
 |   gboolean has_selection = | 
 |       gtk_text_buffer_get_selection_bounds(text_buffer_, &sel_start, &sel_end); | 
 |  | 
 |   // We want the GtkEntry behavior when you move the cursor while you have a | 
 |   // selection.  GtkTextView just drops the selection and moves the cursor, but | 
 |   // instead we want to move the cursor to the appropiate end of the selection. | 
 |   if (step == GTK_MOVEMENT_VISUAL_POSITIONS && !extend_selection && | 
 |       (count == 1 || count == -1) && has_selection) { | 
 |     // We have a selection and start / end are in ascending order. | 
 |     // Cursor placement will remove the selection, so we need inform |model_| | 
 |     // about this change by calling On{Before|After}PossibleChange() methods. | 
 |     OnBeforePossibleChange(); | 
 |     gtk_text_buffer_place_cursor(text_buffer_, | 
 |                                  count == 1 ? &sel_end : &sel_start); | 
 |     OnAfterPossibleChange(); | 
 |   } else if (step == GTK_MOVEMENT_PAGES) {  // Page up and down. | 
 |     // Multiply by count for the direction (if we move too much that's ok). | 
 |     model_->OnUpOrDownKeyPressed(model_->result().size() * count); | 
 |   } else if (step == GTK_MOVEMENT_DISPLAY_LINES) {  // Arrow up and down. | 
 |     model_->OnUpOrDownKeyPressed(count); | 
 |   } else { | 
 |     // Cursor movement may change the selection, we need to record the change | 
 |     // and report it to |model_|. | 
 |     if (has_selection || extend_selection) | 
 |       OnBeforePossibleChange(); | 
 |  | 
 |     // Propagate into GtkTextView | 
 |     GtkTextViewClass* klass = GTK_TEXT_VIEW_GET_CLASS(text_view_); | 
 |     klass->move_cursor(GTK_TEXT_VIEW(text_view_), step, count, | 
 |                        extend_selection); | 
 |  | 
 |     if (has_selection || extend_selection) | 
 |       OnAfterPossibleChange(); | 
 |   } | 
 |  | 
 |   // move-cursor doesn't use a signal accumulator on the return value (it | 
 |   // just ignores then), so we have to stop the propagation. | 
 |   static guint signal_id = g_signal_lookup("move-cursor", GTK_TYPE_TEXT_VIEW); | 
 |   g_signal_stop_emission(text_view_, signal_id, 0); | 
 | } | 
 |  | 
 | void AutocompleteEditViewGtk::HandleViewSizeRequest(GtkWidget* sender, | 
 |                                                     GtkRequisition* req) { | 
 |   // Don't force a minimum width, but use the font-relative height.  This is a | 
 |   // run-first handler, so the default handler was already called. | 
 |   req->width = 1; | 
 | } | 
 |  | 
 | void AutocompleteEditViewGtk::HandlePopulatePopup(GtkWidget* sender, | 
 |                                                   GtkMenu* menu) { | 
 |   GtkWidget* separator = gtk_separator_menu_item_new(); | 
 |   gtk_menu_shell_append(GTK_MENU_SHELL(menu), separator); | 
 |   gtk_widget_show(separator); | 
 |  | 
 |   // Search Engine menu item. | 
 |   GtkWidget* search_engine_menuitem = gtk_menu_item_new_with_mnemonic( | 
 |       gtk_util::ConvertAcceleratorsFromWindowsStyle( | 
 |           l10n_util::GetStringUTF8(IDS_EDIT_SEARCH_ENGINES)).c_str()); | 
 |   gtk_menu_shell_append(GTK_MENU_SHELL(menu), search_engine_menuitem); | 
 |   g_signal_connect(search_engine_menuitem, "activate", | 
 |                    G_CALLBACK(HandleEditSearchEnginesThunk), this); | 
 |   gtk_widget_set_sensitive(search_engine_menuitem, | 
 |       command_updater_->IsCommandEnabled(IDC_EDIT_SEARCH_ENGINES)); | 
 |   gtk_widget_show(search_engine_menuitem); | 
 |  | 
 |   // We need to update the paste and go controller before we know what text | 
 |   // to show. We could do this all asynchronously, but it would be elaborate | 
 |   // because we'd have to account for multiple menus showing, getting called | 
 |   // back after shutdown, and similar issues. | 
 |   GtkClipboard* x_clipboard = gtk_clipboard_get(GDK_SELECTION_CLIPBOARD); | 
 |   gchar* text = gtk_clipboard_wait_for_text(x_clipboard); | 
 |   std::wstring text_wstr = UTF8ToWide(text); | 
 |   g_free(text); | 
 |  | 
 |   // Paste and Go menu item. | 
 |   GtkWidget* paste_go_menuitem = gtk_menu_item_new_with_mnemonic( | 
 |       gtk_util::ConvertAcceleratorsFromWindowsStyle( | 
 |           l10n_util::GetStringUTF8(model_->is_paste_and_search() ? | 
 |               IDS_PASTE_AND_SEARCH : IDS_PASTE_AND_GO)).c_str()); | 
 |   gtk_menu_shell_append(GTK_MENU_SHELL(menu), paste_go_menuitem); | 
 |   g_signal_connect(paste_go_menuitem, "activate", | 
 |                    G_CALLBACK(HandlePasteAndGoThunk), this); | 
 |   gtk_widget_set_sensitive(paste_go_menuitem, | 
 |                            model_->CanPasteAndGo(text_wstr)); | 
 |   gtk_widget_show(paste_go_menuitem); | 
 | } | 
 |  | 
 | void AutocompleteEditViewGtk::HandleEditSearchEngines(GtkWidget* sender) { | 
 |   command_updater_->ExecuteCommand(IDC_EDIT_SEARCH_ENGINES); | 
 | } | 
 |  | 
 | void AutocompleteEditViewGtk::HandlePasteAndGo(GtkWidget* sender) { | 
 |   model_->PasteAndGo(); | 
 | } | 
 |  | 
 | void AutocompleteEditViewGtk::HandleMarkSet(GtkTextBuffer* buffer, | 
 |                                             GtkTextIter* location, | 
 |                                             GtkTextMark* mark) { | 
 |   if (!text_buffer_ || buffer != text_buffer_) | 
 |     return; | 
 |  | 
 |   if (mark != gtk_text_buffer_get_insert(text_buffer_) && | 
 |       mark != gtk_text_buffer_get_selection_bound(text_buffer_)) { | 
 |     return; | 
 |   } | 
 |  | 
 |   // Get the currently-selected text, if there is any. | 
 |   std::string new_selected_text = GetSelectedText(); | 
 |  | 
 | #if defined(OS_CHROMEOS) | 
 |   // If the user just selected some text with the mouse (or at least while the | 
 |   // mouse button was down), make sure that we won't blow their selection away | 
 |   // later by selecting all of the text when the button is released. | 
 |   if (button_1_pressed_ && !new_selected_text.empty()) | 
 |     text_selected_during_click_ = true; | 
 | #endif | 
 |  | 
 |   // If we had some text selected earlier but it's no longer highlighted, we | 
 |   // might need to save it now... | 
 |   if (!selected_text_.empty() && new_selected_text.empty()) { | 
 |     // ... but only if we currently own the selection.  We want to manually | 
 |     // update the selection when the text is unhighlighted because the user | 
 |     // clicked in a blank area of the text view, but not when it's unhighlighted | 
 |     // because another client or widget took the selection.  (This handler gets | 
 |     // called before the default handler, so as long as nobody else took the | 
 |     // selection, the text buffer still owns it even if GTK is about to take it | 
 |     // away in the default handler.) | 
 |     GtkClipboard* clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY); | 
 |     if (gtk_clipboard_get_owner(clipboard) == G_OBJECT(text_buffer_)) | 
 |       SavePrimarySelection(selected_text_); | 
 |   } | 
 |  | 
 |   selected_text_ = new_selected_text; | 
 | } | 
 |  | 
 | // Override the primary selection the text buffer has set. This has to happen | 
 | // after the default handler for the "mark-set" signal. | 
 | void AutocompleteEditViewGtk::HandleMarkSetAfter(GtkTextBuffer* buffer, | 
 |                                                  GtkTextIter* location, | 
 |                                                  GtkTextMark* mark) { | 
 |   UpdatePrimarySelectionIfValidURL(); | 
 | } | 
 |  | 
 | // Just use the default behavior for DnD, except if the drop can be a PasteAndGo | 
 | // then override. | 
 | void AutocompleteEditViewGtk::HandleDragDataReceived( | 
 |     GtkWidget* sender, GdkDragContext* context, gint x, gint y, | 
 |     GtkSelectionData* selection_data, guint target_type, guint time) { | 
 |   // Reset |paste_clipboard_requested_| to make sure we won't misinterpret this | 
 |   // drop action as a paste action. | 
 |   paste_clipboard_requested_ = false; | 
 |  | 
 |   // Don't try to PasteAndGo on drops originating from this omnibox. However, do | 
 |   // allow default behavior for such drags. | 
 |   if (context->source_window == text_view_->window) | 
 |     return; | 
 |  | 
 |   guchar* text = gtk_selection_data_get_text(selection_data); | 
 |   if (!text) | 
 |     return; | 
 |  | 
 |   std::wstring possible_url = UTF8ToWide(reinterpret_cast<char*>(text)); | 
 |   g_free(text); | 
 |   if (model_->CanPasteAndGo(CollapseWhitespace(possible_url, true))) { | 
 |     model_->PasteAndGo(); | 
 |     gtk_drag_finish(context, TRUE, TRUE, time); | 
 |  | 
 |     static guint signal_id = | 
 |         g_signal_lookup("drag-data-received", GTK_TYPE_WIDGET); | 
 |     g_signal_stop_emission(text_view_, signal_id, 0); | 
 |   } | 
 | } | 
 |  | 
 | void AutocompleteEditViewGtk::HandleInsertText( | 
 |     GtkTextBuffer* buffer, GtkTextIter* location, const gchar* text, gint len) { | 
 |   std::string filtered_text; | 
 |   filtered_text.reserve(len); | 
 |  | 
 |   // Filter out new line and tab characters. | 
 |   // |text| is guaranteed to be a valid UTF-8 string, so it's safe here to | 
 |   // filter byte by byte. | 
 |   // | 
 |   // If there was only a single character, then it might be generated by a key | 
 |   // event. In this case, we save the single character to help our | 
 |   // "key-press-event" signal handler distinguish if an Enter key event is | 
 |   // handled by IME or not. | 
 |   if (len == 1 && (text[0] == '\n' || text[0] == '\r')) | 
 |     enter_was_inserted_ = true; | 
 |  | 
 |   for (gint i = 0; i < len; ++i) { | 
 |     gchar c = text[i]; | 
 |     if (c == '\n' || c == '\r' || c == '\t') | 
 |       continue; | 
 |  | 
 |     filtered_text.push_back(c); | 
 |   } | 
 |  | 
 |   if (filtered_text.length()) { | 
 |     // Call the default handler to insert filtered text. | 
 |     GtkTextBufferClass* klass = GTK_TEXT_BUFFER_GET_CLASS(buffer); | 
 |     klass->insert_text(buffer, location, filtered_text.data(), | 
 |                        static_cast<gint>(filtered_text.length())); | 
 |   } | 
 |  | 
 |   // Stop propagating the signal emission to prevent the default handler from | 
 |   // being called again. | 
 |   static guint signal_id = g_signal_lookup("insert-text", GTK_TYPE_TEXT_BUFFER); | 
 |   g_signal_stop_emission(buffer, signal_id, 0); | 
 | } | 
 |  | 
 | void AutocompleteEditViewGtk::HandleBackSpace(GtkWidget* sender) { | 
 |   // Checks if it's currently in keyword search mode. | 
 |   if (model_->is_keyword_hint() || model_->keyword().empty()) | 
 |     return;  // Propgate into GtkTextView. | 
 |  | 
 |   GtkTextIter sel_start, sel_end; | 
 |   // Checks if there is some text selected. | 
 |   if (gtk_text_buffer_get_selection_bounds(text_buffer_, &sel_start, &sel_end)) | 
 |     return;  // Propgate into GtkTextView. | 
 |  | 
 |   GtkTextIter start; | 
 |   gtk_text_buffer_get_start_iter(text_buffer_, &start); | 
 |  | 
 |   if (!gtk_text_iter_equal(&start, &sel_start)) | 
 |     return;  // Propgate into GtkTextView. | 
 |  | 
 |   // We're showing a keyword and the user pressed backspace at the beginning | 
 |   // of the text. Delete the selected keyword. | 
 |   model_->ClearKeyword(GetText()); | 
 |  | 
 |   // Stop propagating the signal emission into GtkTextView. | 
 |   static guint signal_id = g_signal_lookup("backspace", GTK_TYPE_TEXT_VIEW); | 
 |   g_signal_stop_emission(text_view_, signal_id, 0); | 
 | } | 
 |  | 
 | void AutocompleteEditViewGtk::HandleViewMoveFocus(GtkWidget* widget, | 
 |                                                   GtkDirectionType direction) { | 
 |   // Trigger Tab to search behavior only when Tab key is pressed. | 
 |   if (tab_was_pressed_ && enable_tab_to_search_ && | 
 |       model_->is_keyword_hint() && !model_->keyword().empty()) { | 
 |     model_->AcceptKeyword(); | 
 |  | 
 |     // If Tab to search behavior is triggered, then stop the signal emission to | 
 |     // prevent the focus from being moved. | 
 |     static guint signal_id = g_signal_lookup("move-focus", GTK_TYPE_WIDGET); | 
 |     g_signal_stop_emission(widget, signal_id, 0); | 
 |   } | 
 |  | 
 |   // Propagate the signal so that focus can be moved as normal. | 
 | } | 
 |  | 
 | void AutocompleteEditViewGtk::HandleCopyClipboard(GtkWidget* sender) { | 
 |   HandleCopyOrCutClipboard(true); | 
 | } | 
 |  | 
 | void AutocompleteEditViewGtk::HandleCutClipboard(GtkWidget* sender) { | 
 |   HandleCopyOrCutClipboard(false); | 
 | } | 
 |  | 
 | void AutocompleteEditViewGtk::HandleCopyOrCutClipboard(bool copy) { | 
 |   // On copy or cut, we manually update the PRIMARY selection to contain the | 
 |   // highlighted text.  This matches Firefox -- we highlight the URL but don't | 
 |   // update PRIMARY on Ctrl-L, so Ctrl-L, Ctrl-C and then middle-click is a | 
 |   // convenient way to paste the current URL somewhere. | 
 |   if (!gtk_text_buffer_get_has_selection(text_buffer_)) | 
 |     return; | 
 |  | 
 |   GtkClipboard* clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY); | 
 |   DCHECK(clipboard); | 
 |   if (!clipboard) | 
 |     return; | 
 |  | 
 |   CharRange selection = GetSelection(); | 
 |   GURL url; | 
 |   std::wstring text(UTF8ToWide(GetSelectedText())); | 
 |   bool write_url; | 
 |   model_->AdjustTextForCopy(selection.selection_min(), IsSelectAll(), &text, | 
 |                             &url, &write_url); | 
 |  | 
 |   if (write_url) { | 
 |     string16 text16(WideToUTF16(text)); | 
 |     BookmarkDragData data; | 
 |     data.ReadFromTuple(url, text16); | 
 |     data.WriteToClipboard(NULL); | 
 |  | 
 |     // Stop propagating the signal. | 
 |     static guint copy_signal_id = | 
 |         g_signal_lookup("copy-clipboard", GTK_TYPE_TEXT_VIEW); | 
 |     static guint cut_signal_id = | 
 |         g_signal_lookup("cut-clipboard", GTK_TYPE_TEXT_VIEW); | 
 |     g_signal_stop_emission(text_view_, | 
 |                            copy ? copy_signal_id : cut_signal_id, | 
 |                            0); | 
 |  | 
 |     if (!copy) | 
 |       gtk_text_buffer_delete_selection(text_buffer_, true, true); | 
 |   } | 
 |  | 
 |   OwnPrimarySelection(WideToUTF8(text)); | 
 | } | 
 |  | 
 | void AutocompleteEditViewGtk::OwnPrimarySelection(const std::string& text) { | 
 |   primary_selection_text_ = text; | 
 |  | 
 |   GtkTargetList* list = gtk_target_list_new(NULL, 0); | 
 |   gtk_target_list_add_text_targets(list, 0); | 
 |   gint len; | 
 |   GtkTargetEntry* entries = gtk_target_table_new_from_list(list, &len); | 
 |  | 
 |   // When |text_buffer_| is destroyed, it will clear the clipboard, hence | 
 |   // we needn't worry about calling gtk_clipboard_clear(). | 
 |   gtk_clipboard_set_with_owner(gtk_clipboard_get(GDK_SELECTION_PRIMARY), | 
 |                                entries, len, | 
 |                                ClipboardGetSelectionThunk, | 
 |                                ClipboardSelectionCleared, | 
 |                                G_OBJECT(text_buffer_)); | 
 |  | 
 |   gtk_target_list_unref(list); | 
 |   gtk_target_table_free(entries, len); | 
 | } | 
 |  | 
 | void AutocompleteEditViewGtk::HandlePasteClipboard(GtkWidget* sender) { | 
 |   // We can't call model_->on_paste_replacing_all() here, because the actual | 
 |   // paste clipboard action may not be performed if the clipboard is empty. | 
 |   paste_clipboard_requested_ = true; | 
 | } | 
 |  | 
 | gfx::Rect AutocompleteEditViewGtk::WindowBoundsFromIters( | 
 |     GtkTextIter* iter1, GtkTextIter* iter2) { | 
 |   GdkRectangle start_location, end_location; | 
 |   GtkTextView* text_view = GTK_TEXT_VIEW(text_view_); | 
 |   gtk_text_view_get_iter_location(text_view, iter1, &start_location); | 
 |   gtk_text_view_get_iter_location(text_view, iter2, &end_location); | 
 |  | 
 |   gint x1, x2, y1, y2; | 
 |   gtk_text_view_buffer_to_window_coords(text_view, GTK_TEXT_WINDOW_WIDGET, | 
 |                                         start_location.x, start_location.y, | 
 |                                         &x1, &y1); | 
 |   gtk_text_view_buffer_to_window_coords(text_view, GTK_TEXT_WINDOW_WIDGET, | 
 |                                         end_location.x + end_location.width, | 
 |                                         end_location.y + end_location.height, | 
 |                                         &x2, &y2); | 
 |  | 
 |   return gfx::Rect(x1, y1, x2 - x1, y2 - y1); | 
 | } | 
 |  | 
 | gboolean AutocompleteEditViewGtk::HandleExposeEvent(GtkWidget* sender, | 
 |                                                     GdkEventExpose* expose) { | 
 |   if (strikethrough_.cp_min >= strikethrough_.cp_max) | 
 |     return FALSE; | 
 |  | 
 |   gfx::Rect expose_rect(expose->area); | 
 |  | 
 |   GtkTextIter iter_min, iter_max; | 
 |   ItersFromCharRange(strikethrough_, &iter_min, &iter_max); | 
 |   gfx::Rect strikethrough_rect = WindowBoundsFromIters(&iter_min, &iter_max); | 
 |  | 
 |   if (!expose_rect.Intersects(strikethrough_rect)) | 
 |     return FALSE; | 
 |  | 
 |   // Finally, draw. | 
 |   cairo_t* cr = gdk_cairo_create(GDK_DRAWABLE(expose->window)); | 
 |   cairo_rectangle(cr, expose_rect.x(), expose_rect.y(), | 
 |                       expose_rect.width(), expose_rect.height()); | 
 |   cairo_clip(cr); | 
 |  | 
 |   // TODO(estade): we probably shouldn't draw the strikethrough on selected | 
 |   // text. I started to do this, but it was way more effort than it seemed | 
 |   // worth. | 
 |   strikethrough_rect.Inset(kStrikethroughStrokeWidth, | 
 |                            kStrikethroughStrokeWidth); | 
 |   cairo_set_source_rgb(cr, kStrikethroughStrokeRed, 0.0, 0.0); | 
 |   cairo_set_line_width(cr, kStrikethroughStrokeWidth); | 
 |   cairo_set_line_cap(cr, CAIRO_LINE_CAP_ROUND); | 
 |   cairo_move_to(cr, strikethrough_rect.x(), strikethrough_rect.bottom()); | 
 |   cairo_line_to(cr, strikethrough_rect.right(), strikethrough_rect.y()); | 
 |   cairo_stroke(cr); | 
 |   cairo_destroy(cr); | 
 |  | 
 |   return FALSE; | 
 | } | 
 |  | 
 | void AutocompleteEditViewGtk::SelectAllInternal(bool reversed, | 
 |                                                 bool update_primary_selection) { | 
 |   GtkTextIter start, end; | 
 |   if (reversed) { | 
 |     gtk_text_buffer_get_bounds(text_buffer_, &end, &start); | 
 |   } else { | 
 |     gtk_text_buffer_get_bounds(text_buffer_, &start, &end); | 
 |   } | 
 |   if (!update_primary_selection) | 
 |     StartUpdatingHighlightedText(); | 
 |   gtk_text_buffer_select_range(text_buffer_, &start, &end); | 
 |   if (!update_primary_selection) | 
 |     FinishUpdatingHighlightedText(); | 
 | } | 
 |  | 
 | void AutocompleteEditViewGtk::StartUpdatingHighlightedText() { | 
 |   if (GTK_WIDGET_REALIZED(text_view_)) { | 
 |     GtkClipboard* clipboard = | 
 |         gtk_widget_get_clipboard(text_view_, GDK_SELECTION_PRIMARY); | 
 |     DCHECK(clipboard); | 
 |     if (clipboard) | 
 |       gtk_text_buffer_remove_selection_clipboard(text_buffer_, clipboard); | 
 |   } | 
 |   g_signal_handler_block(text_buffer_, mark_set_handler_id_); | 
 |   g_signal_handler_block(text_buffer_, mark_set_handler_id2_); | 
 | } | 
 |  | 
 | void AutocompleteEditViewGtk::FinishUpdatingHighlightedText() { | 
 |   if (GTK_WIDGET_REALIZED(text_view_)) { | 
 |     GtkClipboard* clipboard = | 
 |         gtk_widget_get_clipboard(text_view_, GDK_SELECTION_PRIMARY); | 
 |     DCHECK(clipboard); | 
 |     if (clipboard) | 
 |       gtk_text_buffer_add_selection_clipboard(text_buffer_, clipboard); | 
 |   } | 
 |   g_signal_handler_unblock(text_buffer_, mark_set_handler_id_); | 
 |   g_signal_handler_unblock(text_buffer_, mark_set_handler_id2_); | 
 | } | 
 |  | 
 | AutocompleteEditViewGtk::CharRange AutocompleteEditViewGtk::GetSelection() { | 
 |   // You can not just use get_selection_bounds here, since the order will be | 
 |   // ascending, and you don't know where the user's start and end of the | 
 |   // selection was (if the selection was forwards or backwards).  Get the | 
 |   // actual marks so that we can preserve the selection direction. | 
 |   GtkTextIter start, insert; | 
 |   GtkTextMark* mark; | 
 |  | 
 |   mark = gtk_text_buffer_get_selection_bound(text_buffer_); | 
 |   gtk_text_buffer_get_iter_at_mark(text_buffer_, &start, mark); | 
 |  | 
 |   mark = gtk_text_buffer_get_insert(text_buffer_); | 
 |   gtk_text_buffer_get_iter_at_mark(text_buffer_, &insert, mark); | 
 |  | 
 |   return CharRange(gtk_text_iter_get_offset(&start), | 
 |                    gtk_text_iter_get_offset(&insert)); | 
 | } | 
 |  | 
 | void AutocompleteEditViewGtk::ItersFromCharRange(const CharRange& range, | 
 |                                                  GtkTextIter* iter_min, | 
 |                                                  GtkTextIter* iter_max) { | 
 |   gtk_text_buffer_get_iter_at_offset(text_buffer_, iter_min, range.cp_min); | 
 |   gtk_text_buffer_get_iter_at_offset(text_buffer_, iter_max, range.cp_max); | 
 | } | 
 |  | 
 | int AutocompleteEditViewGtk::GetTextLength() { | 
 |   GtkTextIter start, end; | 
 |   gtk_text_buffer_get_bounds(text_buffer_, &start, &end); | 
 |   return gtk_text_iter_get_offset(&end); | 
 | } | 
 |  | 
 | void AutocompleteEditViewGtk::EmphasizeURLComponents() { | 
 |   // See whether the contents are a URL with a non-empty host portion, which we | 
 |   // should emphasize.  To check for a URL, rather than using the type returned | 
 |   // by Parse(), ask the model, which will check the desired page transition for | 
 |   // this input.  This can tell us whether an UNKNOWN input string is going to | 
 |   // be treated as a search or a navigation, and is the same method the Paste | 
 |   // And Go system uses. | 
 |   url_parse::Component scheme, host; | 
 |   std::wstring text(GetText()); | 
 |   AutocompleteInput::ParseForEmphasizeComponents( | 
 |       text, model_->GetDesiredTLD(), &scheme, &host); | 
 |   const bool emphasize = model_->CurrentTextIsURL() && (host.len > 0); | 
 |  | 
 |   // Set the baseline emphasis. | 
 |   GtkTextIter start, end; | 
 |   gtk_text_buffer_get_bounds(text_buffer_, &start, &end); | 
 |   gtk_text_buffer_remove_all_tags(text_buffer_, &start, &end); | 
 |   if (emphasize) { | 
 |     gtk_text_buffer_apply_tag(text_buffer_, faded_text_tag_, &start, &end); | 
 |  | 
 |     // We've found a host name, give it more emphasis. | 
 |     gtk_text_buffer_get_iter_at_line_index(text_buffer_, &start, 0, | 
 |                                            GetUTF8Offset(text, | 
 |                                                          host.begin)); | 
 |     gtk_text_buffer_get_iter_at_line_index(text_buffer_, &end, 0, | 
 |                                            GetUTF8Offset(text, | 
 |                                                          host.end())); | 
 |  | 
 |     gtk_text_buffer_apply_tag(text_buffer_, normal_text_tag_, &start, &end); | 
 |   } else { | 
 |     gtk_text_buffer_apply_tag(text_buffer_, normal_text_tag_, &start, &end); | 
 |   } | 
 |  | 
 |   strikethrough_ = CharRange(); | 
 |   // Emphasize the scheme for security UI display purposes (if necessary). | 
 |   if (!model_->user_input_in_progress() && scheme.is_nonempty() && | 
 |       (security_level_ != ToolbarModel::NONE)) { | 
 |     CharRange scheme_range = CharRange(GetUTF8Offset(text, scheme.begin), | 
 |                                        GetUTF8Offset(text, scheme.end())); | 
 |     ItersFromCharRange(scheme_range, &start, &end); | 
 |  | 
 |     if (security_level_ == ToolbarModel::SECURITY_ERROR) { | 
 |       strikethrough_ = scheme_range; | 
 |       // When we draw the strikethrough, we don't want to include the ':' at the | 
 |       // end of the scheme. | 
 |       strikethrough_.cp_max--; | 
 |  | 
 |       gtk_text_buffer_apply_tag(text_buffer_, security_error_scheme_tag_, | 
 |                                 &start, &end); | 
 |     } else if (security_level_ == ToolbarModel::SECURITY_WARNING) { | 
 |       gtk_text_buffer_apply_tag(text_buffer_, faded_text_tag_, &start, &end); | 
 |     } else { | 
 |       gtk_text_buffer_apply_tag(text_buffer_, secure_scheme_tag_, &start, &end); | 
 |     } | 
 |   } | 
 | } | 
 |  | 
 | void AutocompleteEditViewGtk::TextChanged() { | 
 |   EmphasizeURLComponents(); | 
 |   controller_->OnChanged(); | 
 | } | 
 |  | 
 | void AutocompleteEditViewGtk::SavePrimarySelection( | 
 |     const std::string& selected_text) { | 
 |   GtkClipboard* clipboard = | 
 |       gtk_widget_get_clipboard(text_view_, GDK_SELECTION_PRIMARY); | 
 |   DCHECK(clipboard); | 
 |   if (!clipboard) | 
 |     return; | 
 |  | 
 |   gtk_clipboard_set_text( | 
 |       clipboard, selected_text.data(), selected_text.size()); | 
 | } | 
 |  | 
 | void AutocompleteEditViewGtk::SetTextAndSelectedRange(const std::wstring& text, | 
 |                                                       const CharRange& range) { | 
 |   std::string utf8 = WideToUTF8(text); | 
 |   gtk_text_buffer_set_text(text_buffer_, utf8.data(), utf8.length()); | 
 |   SetSelectedRange(range); | 
 |   AdjustTextJustification(); | 
 | } | 
 |  | 
 | void AutocompleteEditViewGtk::SetSelectedRange(const CharRange& range) { | 
 |   GtkTextIter insert, bound; | 
 |   ItersFromCharRange(range, &bound, &insert); | 
 |   gtk_text_buffer_select_range(text_buffer_, &insert, &bound); | 
 | } | 
 |  | 
 | void AutocompleteEditViewGtk::AdjustTextJustification() { | 
 |   PangoDirection content_dir = GetContentDirection(); | 
 |  | 
 |   // Use keymap direction if content does not have strong direction. | 
 |   // It matches the behavior of GtkTextView. | 
 |   if (content_dir == PANGO_DIRECTION_NEUTRAL) { | 
 |     content_dir = gdk_keymap_get_direction( | 
 |       gdk_keymap_get_for_display(gtk_widget_get_display(text_view_))); | 
 |   } | 
 |  | 
 |   GtkTextDirection widget_dir = gtk_widget_get_direction(text_view_); | 
 |  | 
 |   if ((widget_dir == GTK_TEXT_DIR_RTL && content_dir == PANGO_DIRECTION_LTR) || | 
 |       (widget_dir == GTK_TEXT_DIR_LTR && content_dir == PANGO_DIRECTION_RTL)) { | 
 |     gtk_text_view_set_justification(GTK_TEXT_VIEW(text_view_), | 
 |                                     GTK_JUSTIFY_RIGHT); | 
 |   } else { | 
 |     gtk_text_view_set_justification(GTK_TEXT_VIEW(text_view_), | 
 |                                     GTK_JUSTIFY_LEFT); | 
 |   } | 
 | } | 
 |  | 
 | PangoDirection AutocompleteEditViewGtk::GetContentDirection() { | 
 |   GtkTextIter iter; | 
 |   gtk_text_buffer_get_start_iter(text_buffer_, &iter); | 
 |  | 
 |   PangoDirection dir = PANGO_DIRECTION_NEUTRAL; | 
 |   do { | 
 |     dir = pango_unichar_direction(gtk_text_iter_get_char(&iter)); | 
 |     if (dir != PANGO_DIRECTION_NEUTRAL) | 
 |       break; | 
 |   } while (gtk_text_iter_forward_char(&iter)); | 
 |  | 
 |   return dir; | 
 | } | 
 |  | 
 | void AutocompleteEditViewGtk::HandleWidgetDirectionChanged( | 
 |     GtkWidget* sender, GtkTextDirection previous_direction) { | 
 |   AdjustTextJustification(); | 
 | } | 
 |  | 
 | void AutocompleteEditViewGtk::HandleKeymapDirectionChanged(GdkKeymap* sender) { | 
 |   AdjustTextJustification(); | 
 | } | 
 |  | 
 | // static | 
 | void AutocompleteEditViewGtk::ClipboardGetSelectionThunk( | 
 |     GtkClipboard* clipboard, | 
 |     GtkSelectionData* selection_data, | 
 |     guint info, | 
 |     gpointer object) { | 
 |   AutocompleteEditViewGtk* edit_view = | 
 |       reinterpret_cast<AutocompleteEditViewGtk*>( | 
 |           g_object_get_data(G_OBJECT(object), kAutocompleteEditViewGtkKey)); | 
 |   edit_view->ClipboardGetSelection(clipboard, selection_data, info); | 
 | } | 
 |  | 
 | void AutocompleteEditViewGtk::ClipboardGetSelection( | 
 |     GtkClipboard* clipboard, | 
 |     GtkSelectionData* selection_data, | 
 |     guint info) { | 
 |   gtk_selection_data_set_text(selection_data, primary_selection_text_.c_str(), | 
 |                               primary_selection_text_.size()); | 
 | } | 
 |  | 
 | std::string AutocompleteEditViewGtk::GetSelectedText() const { | 
 |   GtkTextIter start, end; | 
 |   std::string result; | 
 |   if (gtk_text_buffer_get_selection_bounds(text_buffer_, &start, &end)) { | 
 |     gchar* text = gtk_text_iter_get_text(&start, &end); | 
 |     size_t text_len = strlen(text); | 
 |     if (text_len) | 
 |       result = std::string(text, text_len); | 
 |     g_free(text); | 
 |   } | 
 |   return result; | 
 | } | 
 |  | 
 | void AutocompleteEditViewGtk::UpdatePrimarySelectionIfValidURL() { | 
 |   std::wstring text = UTF8ToWide(GetSelectedText()); | 
 |  | 
 |   if (text.empty()) | 
 |     return; | 
 |  | 
 |   // Use AdjustTextForCopy to make sure we prefix the text with 'http://'. | 
 |   CharRange selection = GetSelection(); | 
 |   GURL url; | 
 |   bool write_url; | 
 |   model_->AdjustTextForCopy(selection.selection_min(), IsSelectAll(), &text, | 
 |                             &url, &write_url); | 
 |   if (write_url) { | 
 |     selected_text_ = WideToUTF8(text); | 
 |     OwnPrimarySelection(selected_text_); | 
 |   } | 
 | } |