• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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/autocomplete/autocomplete_edit_view_gtk.h"
6 
7 #include <gdk/gdkkeysyms.h>
8 #include <gtk/gtk.h>
9 
10 #include <algorithm>
11 
12 #include "base/logging.h"
13 #include "base/string_util.h"
14 #include "base/utf_string_conversions.h"
15 #include "chrome/app/chrome_command_ids.h"
16 #include "chrome/browser/autocomplete/autocomplete_edit.h"
17 #include "chrome/browser/autocomplete/autocomplete_match.h"
18 #include "chrome/browser/autocomplete/autocomplete_popup_model.h"
19 #include "chrome/browser/bookmarks/bookmark_node_data.h"
20 #include "chrome/browser/command_updater.h"
21 #include "chrome/browser/defaults.h"
22 #include "chrome/browser/instant/instant_controller.h"
23 #include "chrome/browser/platform_util.h"
24 #include "chrome/browser/ui/gtk/gtk_util.h"
25 #include "chrome/browser/ui/gtk/view_id_util.h"
26 #include "chrome/browser/ui/toolbar/toolbar_model.h"
27 #include "content/browser/tab_contents/tab_contents.h"
28 #include "content/common/notification_service.h"
29 #include "googleurl/src/gurl.h"
30 #include "grit/generated_resources.h"
31 #include "net/base/escape.h"
32 #include "third_party/undoview/undo_view.h"
33 #include "ui/base/animation/multi_animation.h"
34 #include "ui/base/dragdrop/drag_drop_types.h"
35 #include "ui/base/l10n/l10n_util.h"
36 #include "ui/base/resource/resource_bundle.h"
37 #include "ui/gfx/color_utils.h"
38 #include "ui/gfx/font.h"
39 #include "ui/gfx/gtk_util.h"
40 #include "ui/gfx/skia_utils_gtk.h"
41 
42 #if defined(TOOLKIT_VIEWS)
43 #include "chrome/browser/autocomplete/autocomplete_edit_view_views.h"
44 #include "chrome/browser/ui/views/autocomplete/autocomplete_popup_contents_view.h"
45 #include "chrome/browser/ui/views/location_bar/location_bar_view.h"
46 #include "views/controls/textfield/native_textfield_views.h"
47 #include "views/events/event.h"
48 #else
49 #include "chrome/browser/autocomplete/autocomplete_popup_view_gtk.h"
50 #include "chrome/browser/ui/gtk/gtk_theme_service.h"
51 #include "chrome/browser/ui/gtk/location_bar_view_gtk.h"
52 #endif
53 
54 namespace {
55 
56 const gchar* kAutocompleteEditViewGtkKey = "__ACE_VIEW_GTK__";
57 
58 const char kTextBaseColor[] = "#808080";
59 const char kSecureSchemeColor[] = "#079500";
60 const char kSecurityErrorSchemeColor[] = "#a20000";
61 
62 const double kStrikethroughStrokeRed = 162.0 / 256.0;
63 const double kStrikethroughStrokeWidth = 2.0;
64 
GetUTF8Offset(const string16 & text,size_t text_offset)65 size_t GetUTF8Offset(const string16& text, size_t text_offset) {
66   return UTF16ToUTF8(text.substr(0, text_offset)).size();
67 }
68 
69 // A helper method for determining a valid drag operation given the allowed
70 // operation.  We prefer copy over link.
CopyOrLinkDragOperation(int drag_operation)71 int CopyOrLinkDragOperation(int drag_operation) {
72   if (drag_operation & ui::DragDropTypes::DRAG_COPY)
73     return ui::DragDropTypes::DRAG_COPY;
74   if (drag_operation & ui::DragDropTypes::DRAG_LINK)
75     return ui::DragDropTypes::DRAG_LINK;
76   return ui::DragDropTypes::DRAG_NONE;
77 }
78 
79 // Stores GTK+-specific state so it can be restored after switching tabs.
80 struct ViewState {
ViewState__anon9928f9f50111::ViewState81   explicit ViewState(const AutocompleteEditViewGtk::CharRange& selection_range)
82       : selection_range(selection_range) {
83   }
84 
85   // Range of selected text.
86   AutocompleteEditViewGtk::CharRange selection_range;
87 };
88 
89 struct AutocompleteEditState {
AutocompleteEditState__anon9928f9f50111::AutocompleteEditState90   AutocompleteEditState(const AutocompleteEditModel::State& model_state,
91                         const ViewState& view_state)
92       : model_state(model_state),
93         view_state(view_state) {
94   }
95 
96   const AutocompleteEditModel::State model_state;
97   const ViewState view_state;
98 };
99 
100 // Returns a lazily initialized property bag accessor for saving our state in a
101 // TabContents.
GetStateAccessor()102 PropertyAccessor<AutocompleteEditState>* GetStateAccessor() {
103   static PropertyAccessor<AutocompleteEditState> state;
104   return &state;
105 }
106 
107 // Set up style properties to override the default GtkTextView; if a theme has
108 // overridden some of these properties, an inner-line will be displayed inside
109 // the fake GtkTextEntry.
SetEntryStyle()110 void SetEntryStyle() {
111   static bool style_was_set = false;
112 
113   if (style_was_set)
114     return;
115   style_was_set = true;
116 
117   gtk_rc_parse_string(
118       "style \"chrome-location-bar-entry\" {"
119       "  xthickness = 0\n"
120       "  ythickness = 0\n"
121       "  GtkWidget::focus_padding = 0\n"
122       "  GtkWidget::focus-line-width = 0\n"
123       "  GtkWidget::interior_focus = 0\n"
124       "  GtkWidget::internal-padding = 0\n"
125       "  GtkContainer::border-width = 0\n"
126       "}\n"
127       "widget \"*chrome-location-bar-entry\" "
128       "style \"chrome-location-bar-entry\"");
129 }
130 
131 // Copied from GTK+. Called when we lose the primary selection. This will clear
132 // the selection in the text buffer.
ClipboardSelectionCleared(GtkClipboard * clipboard,gpointer data)133 void ClipboardSelectionCleared(GtkClipboard* clipboard,
134                                gpointer data) {
135   GtkTextIter insert;
136   GtkTextIter selection_bound;
137   GtkTextBuffer* buffer = GTK_TEXT_BUFFER(data);
138 
139   gtk_text_buffer_get_iter_at_mark(buffer, &insert,
140                                    gtk_text_buffer_get_insert(buffer));
141   gtk_text_buffer_get_iter_at_mark(buffer, &selection_bound,
142                                    gtk_text_buffer_get_selection_bound(buffer));
143 
144   if (!gtk_text_iter_equal(&insert, &selection_bound)) {
145     gtk_text_buffer_move_mark(buffer,
146                               gtk_text_buffer_get_selection_bound(buffer),
147                               &insert);
148   }
149 }
150 
151 }  // namespace
152 
AutocompleteEditViewGtk(AutocompleteEditController * controller,ToolbarModel * toolbar_model,Profile * profile,CommandUpdater * command_updater,bool popup_window_mode,views::View * location_bar)153 AutocompleteEditViewGtk::AutocompleteEditViewGtk(
154     AutocompleteEditController* controller,
155     ToolbarModel* toolbar_model,
156     Profile* profile,
157     CommandUpdater* command_updater,
158     bool popup_window_mode,
159 #if defined(TOOLKIT_VIEWS)
160     views::View* location_bar)
161 #else
162     GtkWidget* location_bar)
163 #endif
164     : text_view_(NULL),
165       tag_table_(NULL),
166       text_buffer_(NULL),
167       faded_text_tag_(NULL),
168       secure_scheme_tag_(NULL),
169       security_error_scheme_tag_(NULL),
170       normal_text_tag_(NULL),
171       instant_anchor_tag_(NULL),
172       instant_view_(NULL),
173       instant_mark_(NULL),
174       model_(new AutocompleteEditModel(this, controller, profile)),
175       controller_(controller),
176       toolbar_model_(toolbar_model),
177       command_updater_(command_updater),
178       popup_window_mode_(popup_window_mode),
179       security_level_(ToolbarModel::NONE),
180       mark_set_handler_id_(0),
181 #if defined(OS_CHROMEOS)
182       button_1_pressed_(false),
183       text_selected_during_click_(false),
184       text_view_focused_before_button_press_(false),
185 #endif
186 #if defined(TOOLKIT_VIEWS)
187       location_bar_view_(location_bar),
188 #else
189       theme_service_(GtkThemeService::GetFrom(profile)),
190 #endif
191       enter_was_pressed_(false),
192       tab_was_pressed_(false),
193       paste_clipboard_requested_(false),
194       enter_was_inserted_(false),
195       selection_suggested_(false),
196       delete_was_pressed_(false),
197       delete_at_end_pressed_(false),
198       handling_key_press_(false),
199       content_maybe_changed_by_key_press_(false),
200       update_popup_without_focus_(false),
201 #if GTK_CHECK_VERSION(2, 20, 0)
202       preedit_size_before_change_(0),
203 #endif
204       going_to_focus_(NULL) {
205   popup_view_.reset(
206 #if defined(TOOLKIT_VIEWS)
207       new AutocompletePopupContentsView
208 #else
209       new AutocompletePopupViewGtk
210 #endif
211           (GetFont(), this, model_.get(), profile, location_bar));
212 }
213 
~AutocompleteEditViewGtk()214 AutocompleteEditViewGtk::~AutocompleteEditViewGtk() {
215   NotificationService::current()->Notify(
216       NotificationType::AUTOCOMPLETE_EDIT_DESTROYED,
217       Source<AutocompleteEditViewGtk>(this),
218       NotificationService::NoDetails());
219 
220   // Explicitly teardown members which have a reference to us.  Just to be safe
221   // we want them to be destroyed before destroying any other internal state.
222   popup_view_.reset();
223   model_.reset();
224 
225   // We own our widget and TextView related objects.
226   if (alignment_.get()) {  // Init() has been called.
227     alignment_.Destroy();
228     g_object_unref(text_buffer_);
229     g_object_unref(tag_table_);
230     // The tags we created are owned by the tag_table, and should be destroyed
231     // along with it.  We don't hold our own reference to them.
232   }
233 }
234 
Init()235 void AutocompleteEditViewGtk::Init() {
236   SetEntryStyle();
237 
238   // The height of the text view is going to change based on the font used.  We
239   // don't want to stretch the height, and we want it vertically centered.
240   alignment_.Own(gtk_alignment_new(0., 0.5, 1.0, 0.0));
241   gtk_widget_set_name(alignment_.get(),
242                       "chrome-autocomplete-edit-view");
243 
244   // The GtkTagTable and GtkTextBuffer are not initially unowned, so we have
245   // our own reference when we create them, and we own them.  Adding them to
246   // the other objects adds a reference; it doesn't adopt them.
247   tag_table_ = gtk_text_tag_table_new();
248   text_buffer_ = gtk_text_buffer_new(tag_table_);
249   g_object_set_data(G_OBJECT(text_buffer_), kAutocompleteEditViewGtkKey, this);
250 
251   // We need to run this two handlers before undo manager's handlers, so that
252   // text iterators modified by these handlers can be passed down to undo
253   // manager's handlers.
254   g_signal_connect(text_buffer_, "delete-range",
255                    G_CALLBACK(&HandleDeleteRangeThunk), this);
256   g_signal_connect(text_buffer_, "mark-set",
257                    G_CALLBACK(&HandleMarkSetAlwaysThunk), this);
258 
259   text_view_ = gtk_undo_view_new(text_buffer_);
260   if (popup_window_mode_)
261     gtk_text_view_set_editable(GTK_TEXT_VIEW(text_view_), false);
262 
263   // One pixel left margin is necessary to make the cursor visible when UI
264   // language direction is LTR but |text_buffer_|'s content direction is RTL.
265   gtk_text_view_set_left_margin(GTK_TEXT_VIEW(text_view_), 1);
266 
267   // See SetEntryStyle() comments.
268   gtk_widget_set_name(text_view_, "chrome-location-bar-entry");
269 
270   // The text view was floating.  It will now be owned by the alignment.
271   gtk_container_add(GTK_CONTAINER(alignment_.get()), text_view_);
272 
273   // Do not allow inserting tab characters when pressing Tab key, so that when
274   // Tab key is pressed, |text_view_| will emit "move-focus" signal, which will
275   // be intercepted by our own handler to trigger Tab to search feature when
276   // necessary.
277   gtk_text_view_set_accepts_tab(GTK_TEXT_VIEW(text_view_), FALSE);
278 
279   faded_text_tag_ = gtk_text_buffer_create_tag(text_buffer_,
280       NULL, "foreground", kTextBaseColor, NULL);
281   secure_scheme_tag_ = gtk_text_buffer_create_tag(text_buffer_,
282       NULL, "foreground", kSecureSchemeColor, NULL);
283   security_error_scheme_tag_ = gtk_text_buffer_create_tag(text_buffer_,
284       NULL, "foreground", kSecurityErrorSchemeColor, NULL);
285   normal_text_tag_ = gtk_text_buffer_create_tag(text_buffer_,
286       NULL, "foreground", "#000000", NULL);
287 
288   // NOTE: This code used to connect to "changed", however this was fired too
289   // often and during bad times (our own buffer changes?).  It works out much
290   // better to listen to end-user-action, which should be fired whenever the
291   // user makes some sort of change to the buffer.
292   g_signal_connect(text_buffer_, "begin-user-action",
293                    G_CALLBACK(&HandleBeginUserActionThunk), this);
294   g_signal_connect(text_buffer_, "end-user-action",
295                    G_CALLBACK(&HandleEndUserActionThunk), this);
296   // We connect to key press and release for special handling of a few keys.
297   g_signal_connect(text_view_, "key-press-event",
298                    G_CALLBACK(&HandleKeyPressThunk), this);
299   g_signal_connect(text_view_, "key-release-event",
300                    G_CALLBACK(&HandleKeyReleaseThunk), this);
301   g_signal_connect(text_view_, "button-press-event",
302                    G_CALLBACK(&HandleViewButtonPressThunk), this);
303   g_signal_connect(text_view_, "button-release-event",
304                    G_CALLBACK(&HandleViewButtonReleaseThunk), this);
305   g_signal_connect(text_view_, "focus-in-event",
306                    G_CALLBACK(&HandleViewFocusInThunk), this);
307   g_signal_connect(text_view_, "focus-out-event",
308                    G_CALLBACK(&HandleViewFocusOutThunk), this);
309   // NOTE: The GtkTextView documentation asks you not to connect to this
310   // signal, but it is very convenient and clean for catching up/down.
311   g_signal_connect(text_view_, "move-cursor",
312                    G_CALLBACK(&HandleViewMoveCursorThunk), this);
313   g_signal_connect(text_view_, "move-focus",
314                    G_CALLBACK(&HandleViewMoveFocusThunk), this);
315   // Override the size request.  We want to keep the original height request
316   // from the widget, since that's font dependent.  We want to ignore the width
317   // so we don't force a minimum width based on the text length.
318   g_signal_connect(text_view_, "size-request",
319                    G_CALLBACK(&HandleViewSizeRequestThunk), this);
320   g_signal_connect(text_view_, "populate-popup",
321                    G_CALLBACK(&HandlePopulatePopupThunk), this);
322   mark_set_handler_id_ = g_signal_connect(
323       text_buffer_, "mark-set", G_CALLBACK(&HandleMarkSetThunk), this);
324   mark_set_handler_id2_ = g_signal_connect_after(
325       text_buffer_, "mark-set", G_CALLBACK(&HandleMarkSetAfterThunk), this);
326   g_signal_connect(text_view_, "drag-data-received",
327                    G_CALLBACK(&HandleDragDataReceivedThunk), this);
328   // Override the text_view_'s default drag-data-get handler by calling our own
329   // version after the normal call has happened.
330   g_signal_connect_after(text_view_, "drag-data-get",
331                    G_CALLBACK(&HandleDragDataGetThunk), this);
332   g_signal_connect(text_view_, "backspace",
333                    G_CALLBACK(&HandleBackSpaceThunk), this);
334   g_signal_connect(text_view_, "copy-clipboard",
335                    G_CALLBACK(&HandleCopyClipboardThunk), this);
336   g_signal_connect(text_view_, "cut-clipboard",
337                    G_CALLBACK(&HandleCutClipboardThunk), this);
338   g_signal_connect(text_view_, "paste-clipboard",
339                    G_CALLBACK(&HandlePasteClipboardThunk), this);
340   g_signal_connect_after(text_view_, "expose-event",
341                          G_CALLBACK(&HandleExposeEventThunk), this);
342   g_signal_connect(text_view_, "direction-changed",
343                    G_CALLBACK(&HandleWidgetDirectionChangedThunk), this);
344   g_signal_connect(text_view_, "delete-from-cursor",
345                    G_CALLBACK(&HandleDeleteFromCursorThunk), this);
346   g_signal_connect(text_view_, "hierarchy-changed",
347                    G_CALLBACK(&HandleHierarchyChangedThunk), this);
348 #if GTK_CHECK_VERSION(2, 20, 0)
349   g_signal_connect(text_view_, "preedit-changed",
350                    G_CALLBACK(&HandlePreeditChangedThunk), this);
351 #endif
352   g_signal_connect(text_view_, "undo", G_CALLBACK(&HandleUndoRedoThunk), this);
353   g_signal_connect(text_view_, "redo", G_CALLBACK(&HandleUndoRedoThunk), this);
354   g_signal_connect_after(text_view_, "undo",
355                          G_CALLBACK(&HandleUndoRedoAfterThunk), this);
356   g_signal_connect_after(text_view_, "redo",
357                          G_CALLBACK(&HandleUndoRedoAfterThunk), this);
358   g_signal_connect(text_view_, "destroy",
359                    G_CALLBACK(&gtk_widget_destroyed), &text_view_);
360 
361   // Setup for the Instant suggestion text view.
362   // GtkLabel is used instead of GtkTextView to get transparent background.
363   instant_view_ = gtk_label_new(NULL);
364   gtk_widget_set_no_show_all(instant_view_, TRUE);
365   gtk_label_set_selectable(GTK_LABEL(instant_view_), TRUE);
366 
367   GtkTextIter end_iter;
368   gtk_text_buffer_get_end_iter(text_buffer_, &end_iter);
369 
370   // Insert a Zero Width Space character just before the instant anchor.
371   // It's a hack to workaround a bug of GtkTextView which can not align the
372   // preedit string and a child anchor correctly when there is no other content
373   // around the preedit string.
374   gtk_text_buffer_insert(text_buffer_, &end_iter, "\342\200\213", -1);
375   GtkTextChildAnchor* instant_anchor =
376       gtk_text_buffer_create_child_anchor(text_buffer_, &end_iter);
377 
378   gtk_text_view_add_child_at_anchor(GTK_TEXT_VIEW(text_view_),
379                                     instant_view_,
380                                     instant_anchor);
381 
382   instant_anchor_tag_ = gtk_text_buffer_create_tag(text_buffer_, NULL, NULL);
383 
384   GtkTextIter anchor_iter;
385   gtk_text_buffer_get_iter_at_child_anchor(text_buffer_, &anchor_iter,
386                                            instant_anchor);
387   gtk_text_buffer_apply_tag(text_buffer_, instant_anchor_tag_,
388                             &anchor_iter, &end_iter);
389 
390   GtkTextIter start_iter;
391   gtk_text_buffer_get_start_iter(text_buffer_, &start_iter);
392   instant_mark_ =
393       gtk_text_buffer_create_mark(text_buffer_, NULL, &start_iter, FALSE);
394 
395   // Hooking up this handler after setting up above hacks for Instant view, so
396   // that we won't filter out the special ZWP mark itself.
397   g_signal_connect(text_buffer_, "insert-text",
398                    G_CALLBACK(&HandleInsertTextThunk), this);
399 
400   AdjustVerticalAlignmentOfInstantView();
401 
402   ui::MultiAnimation::Parts parts;
403   parts.push_back(ui::MultiAnimation::Part(
404       InstantController::kAutoCommitPauseTimeMS, ui::Tween::ZERO));
405   parts.push_back(ui::MultiAnimation::Part(
406       InstantController::kAutoCommitFadeInTimeMS, ui::Tween::EASE_IN));
407   instant_animation_.reset(new ui::MultiAnimation(parts));
408   instant_animation_->set_continuous(false);
409 
410 #if !defined(TOOLKIT_VIEWS)
411   registrar_.Add(this,
412                  NotificationType::BROWSER_THEME_CHANGED,
413                  NotificationService::AllSources());
414   theme_service_->InitThemesFor(this);
415 #else
416   // Manually invoke SetBaseColor() because TOOLKIT_VIEWS doesn't observe
417   // themes.
418   SetBaseColor();
419 #endif
420 
421   ViewIDUtil::SetID(GetNativeView(), VIEW_ID_AUTOCOMPLETE);
422 }
423 
HandleHierarchyChanged(GtkWidget * sender,GtkWidget * old_toplevel)424 void AutocompleteEditViewGtk::HandleHierarchyChanged(
425     GtkWidget* sender, GtkWidget* old_toplevel) {
426   GtkWindow* new_toplevel = platform_util::GetTopLevel(sender);
427   if (!new_toplevel)
428     return;
429 
430   // Use |signals_| to make sure we don't get called back after destruction.
431   signals_.Connect(new_toplevel, "set-focus",
432                    G_CALLBACK(&HandleWindowSetFocusThunk), this);
433 }
434 
SetFocus()435 void AutocompleteEditViewGtk::SetFocus() {
436   DCHECK(text_view_);
437   gtk_widget_grab_focus(text_view_);
438 }
439 
WidthOfTextAfterCursor()440 int AutocompleteEditViewGtk::WidthOfTextAfterCursor() {
441   // Not used.
442   return -1;
443 }
444 
model()445 AutocompleteEditModel* AutocompleteEditViewGtk::model() {
446   return model_.get();
447 }
448 
model() const449 const AutocompleteEditModel* AutocompleteEditViewGtk::model() const {
450   return model_.get();
451 }
452 
SaveStateToTab(TabContents * tab)453 void AutocompleteEditViewGtk::SaveStateToTab(TabContents* tab) {
454   DCHECK(tab);
455   // If any text has been selected, register it as the PRIMARY selection so it
456   // can still be pasted via middle-click after the text view is cleared.
457   if (!selected_text_.empty())
458     SavePrimarySelection(selected_text_);
459   // NOTE: GetStateForTabSwitch may affect GetSelection, so order is important.
460   AutocompleteEditModel::State model_state = model_->GetStateForTabSwitch();
461   GetStateAccessor()->SetProperty(
462       tab->property_bag(),
463       AutocompleteEditState(model_state, ViewState(GetSelection())));
464 }
465 
Update(const TabContents * contents)466 void AutocompleteEditViewGtk::Update(const TabContents* contents) {
467   // NOTE: We're getting the URL text here from the ToolbarModel.
468   bool visibly_changed_permanent_text =
469       model_->UpdatePermanentText(WideToUTF16Hack(toolbar_model_->GetText()));
470 
471   ToolbarModel::SecurityLevel security_level =
472         toolbar_model_->GetSecurityLevel();
473   bool changed_security_level = (security_level != security_level_);
474   security_level_ = security_level;
475 
476   if (contents) {
477     selected_text_.clear();
478     RevertAll();
479     const AutocompleteEditState* state =
480         GetStateAccessor()->GetProperty(contents->property_bag());
481     if (state) {
482       model_->RestoreState(state->model_state);
483 
484       // Move the marks for the cursor and the other end of the selection to
485       // the previously-saved offsets (but preserve PRIMARY).
486       StartUpdatingHighlightedText();
487       SetSelectedRange(state->view_state.selection_range);
488       FinishUpdatingHighlightedText();
489     }
490   } else if (visibly_changed_permanent_text) {
491     RevertAll();
492     // TODO(deanm): There should be code to restore select all here.
493   } else if (changed_security_level) {
494     EmphasizeURLComponents();
495   }
496 }
497 
OpenURL(const GURL & url,WindowOpenDisposition disposition,PageTransition::Type transition,const GURL & alternate_nav_url,size_t selected_line,const string16 & keyword)498 void AutocompleteEditViewGtk::OpenURL(const GURL& url,
499                                       WindowOpenDisposition disposition,
500                                       PageTransition::Type transition,
501                                       const GURL& alternate_nav_url,
502                                       size_t selected_line,
503                                       const string16& keyword) {
504   if (!url.is_valid())
505     return;
506 
507   model_->OpenURL(url, disposition, transition, alternate_nav_url,
508                   selected_line, keyword);
509 }
510 
GetText() const511 string16 AutocompleteEditViewGtk::GetText() const {
512   GtkTextIter start, end;
513   GetTextBufferBounds(&start, &end);
514   gchar* utf8 = gtk_text_buffer_get_text(text_buffer_, &start, &end, false);
515   string16 out(UTF8ToUTF16(utf8));
516   g_free(utf8);
517 
518 #if GTK_CHECK_VERSION(2, 20, 0)
519   // We need to treat the text currently being composed by the input method as
520   // part of the text content, so that omnibox can work correctly in the middle
521   // of composition.
522   if (preedit_.size()) {
523     GtkTextMark* mark = gtk_text_buffer_get_insert(text_buffer_);
524     gtk_text_buffer_get_iter_at_mark(text_buffer_, &start, mark);
525     out.insert(gtk_text_iter_get_offset(&start), preedit_);
526   }
527 #endif
528   return out;
529 }
530 
IsEditingOrEmpty() const531 bool AutocompleteEditViewGtk::IsEditingOrEmpty() const {
532   return model_->user_input_in_progress() || (GetTextLength() == 0);
533 }
534 
GetIcon() const535 int AutocompleteEditViewGtk::GetIcon() const {
536   return IsEditingOrEmpty() ?
537       AutocompleteMatch::TypeToIcon(model_->CurrentTextType()) :
538       toolbar_model_->GetIcon();
539 }
540 
SetUserText(const string16 & text)541 void AutocompleteEditViewGtk::SetUserText(const string16& text) {
542   SetUserText(text, text, true);
543 }
544 
SetUserText(const string16 & text,const string16 & display_text,bool update_popup)545 void AutocompleteEditViewGtk::SetUserText(const string16& text,
546                                           const string16& display_text,
547                                           bool update_popup) {
548   model_->SetUserText(text);
549   // TODO(deanm): something about selection / focus change here.
550   SetWindowTextAndCaretPos(display_text, display_text.length());
551   if (update_popup)
552     UpdatePopup();
553   TextChanged();
554 }
555 
SetWindowTextAndCaretPos(const string16 & text,size_t caret_pos)556 void AutocompleteEditViewGtk::SetWindowTextAndCaretPos(const string16& text,
557                                                        size_t caret_pos) {
558   CharRange range(static_cast<int>(caret_pos), static_cast<int>(caret_pos));
559   SetTextAndSelectedRange(text, range);
560 }
561 
SetForcedQuery()562 void AutocompleteEditViewGtk::SetForcedQuery() {
563   const string16 current_text(GetText());
564   const size_t start = current_text.find_first_not_of(kWhitespaceUTF16);
565   if (start == string16::npos || (current_text[start] != '?')) {
566     SetUserText(ASCIIToUTF16("?"));
567   } else {
568     StartUpdatingHighlightedText();
569     SetSelectedRange(CharRange(current_text.size(), start + 1));
570     FinishUpdatingHighlightedText();
571   }
572 }
573 
IsSelectAll()574 bool AutocompleteEditViewGtk::IsSelectAll() {
575   GtkTextIter sel_start, sel_end;
576   gtk_text_buffer_get_selection_bounds(text_buffer_, &sel_start, &sel_end);
577 
578   GtkTextIter start, end;
579   GetTextBufferBounds(&start, &end);
580 
581   // Returns true if the |text_buffer_| is empty.
582   return gtk_text_iter_equal(&start, &sel_start) &&
583       gtk_text_iter_equal(&end, &sel_end);
584 }
585 
DeleteAtEndPressed()586 bool AutocompleteEditViewGtk::DeleteAtEndPressed() {
587   return delete_at_end_pressed_;
588 }
589 
GetSelectionBounds(string16::size_type * start,string16::size_type * end)590 void AutocompleteEditViewGtk::GetSelectionBounds(string16::size_type* start,
591                                                  string16::size_type* end) {
592   CharRange selection = GetSelection();
593   *start = static_cast<size_t>(selection.cp_min);
594   *end = static_cast<size_t>(selection.cp_max);
595 }
596 
SelectAll(bool reversed)597 void AutocompleteEditViewGtk::SelectAll(bool reversed) {
598   // SelectAll() is invoked as a side effect of other actions (e.g.  switching
599   // tabs or hitting Escape) in autocomplete_edit.cc, so we don't update the
600   // PRIMARY selection here.
601   SelectAllInternal(reversed, false);
602 }
603 
RevertAll()604 void AutocompleteEditViewGtk::RevertAll() {
605   ClosePopup();
606   model_->Revert();
607   TextChanged();
608 }
609 
UpdatePopup()610 void AutocompleteEditViewGtk::UpdatePopup() {
611   model_->SetInputInProgress(true);
612   if (!update_popup_without_focus_ && !model_->has_focus())
613     return;
614 
615   // Don't inline autocomplete when the caret/selection isn't at the end of
616   // the text, or in the middle of composition.
617   CharRange sel = GetSelection();
618   bool no_inline_autocomplete =
619       std::max(sel.cp_max, sel.cp_min) < GetTextLength() || IsImeComposing();
620   model_->StartAutocomplete(sel.cp_min != sel.cp_max, no_inline_autocomplete);
621 }
622 
ClosePopup()623 void AutocompleteEditViewGtk::ClosePopup() {
624   model_->StopAutocomplete();
625 }
626 
OnTemporaryTextMaybeChanged(const string16 & display_text,bool save_original_selection)627 void AutocompleteEditViewGtk::OnTemporaryTextMaybeChanged(
628     const string16& display_text,
629     bool save_original_selection) {
630   if (save_original_selection)
631     saved_temporary_selection_ = GetSelection();
632 
633   StartUpdatingHighlightedText();
634   SetWindowTextAndCaretPos(display_text, display_text.length());
635   FinishUpdatingHighlightedText();
636   TextChanged();
637 }
638 
OnInlineAutocompleteTextMaybeChanged(const string16 & display_text,size_t user_text_length)639 bool AutocompleteEditViewGtk::OnInlineAutocompleteTextMaybeChanged(
640     const string16& display_text,
641     size_t user_text_length) {
642   if (display_text == GetText())
643     return false;
644 
645   StartUpdatingHighlightedText();
646   CharRange range(display_text.size(), user_text_length);
647   SetTextAndSelectedRange(display_text, range);
648   FinishUpdatingHighlightedText();
649   TextChanged();
650   return true;
651 }
652 
OnRevertTemporaryText()653 void AutocompleteEditViewGtk::OnRevertTemporaryText() {
654   StartUpdatingHighlightedText();
655   SetSelectedRange(saved_temporary_selection_);
656   FinishUpdatingHighlightedText();
657   TextChanged();
658 }
659 
OnBeforePossibleChange()660 void AutocompleteEditViewGtk::OnBeforePossibleChange() {
661   // Record this paste, so we can do different behavior.
662   if (paste_clipboard_requested_) {
663     paste_clipboard_requested_ = false;
664     model_->on_paste();
665   }
666 
667   // This method will be called in HandleKeyPress() method just before
668   // handling a key press event. So we should prevent it from being called
669   // when handling the key press event.
670   if (handling_key_press_)
671     return;
672 
673   // Record our state.
674   text_before_change_ = GetText();
675   sel_before_change_ = GetSelection();
676 #if GTK_CHECK_VERSION(2, 20, 0)
677   preedit_size_before_change_ = preedit_.size();
678 #endif
679 }
680 
681 // TODO(deanm): This is mostly stolen from Windows, and will need some work.
OnAfterPossibleChange()682 bool AutocompleteEditViewGtk::OnAfterPossibleChange() {
683   // This method will be called in HandleKeyPress() method just after
684   // handling a key press event. So we should prevent it from being called
685   // when handling the key press event.
686   if (handling_key_press_) {
687     content_maybe_changed_by_key_press_ = true;
688     return false;
689   }
690 
691   // If the change is caused by an Enter key press event, and the event was not
692   // handled by IME, then it's an unexpected change and shall be reverted here.
693   // {Start|Finish}UpdatingHighlightedText() are called here to prevent the
694   // PRIMARY selection from being changed.
695   if (enter_was_pressed_ && enter_was_inserted_) {
696     StartUpdatingHighlightedText();
697     SetTextAndSelectedRange(text_before_change_, sel_before_change_);
698     FinishUpdatingHighlightedText();
699     return false;
700   }
701 
702   const CharRange new_sel = GetSelection();
703   const int length = GetTextLength();
704   const bool selection_differs =
705       ((new_sel.cp_min != new_sel.cp_max) ||
706        (sel_before_change_.cp_min != sel_before_change_.cp_max)) &&
707       ((new_sel.cp_min != sel_before_change_.cp_min) ||
708        (new_sel.cp_max != sel_before_change_.cp_max));
709   const bool at_end_of_edit =
710       (new_sel.cp_min == length && new_sel.cp_max == length);
711 
712   // See if the text or selection have changed since OnBeforePossibleChange().
713   const string16 new_text(GetText());
714   text_changed_ = (new_text != text_before_change_);
715 #if GTK_CHECK_VERSION(2, 20, 0)
716   text_changed_ =
717       text_changed_ || (preedit_.size() != preedit_size_before_change_);
718 #endif
719 
720   if (text_changed_)
721     AdjustTextJustification();
722 
723   // When the user has deleted text, we don't allow inline autocomplete.  Make
724   // sure to not flag cases like selecting part of the text and then pasting
725   // (or typing) the prefix of that selection.  (We detect these by making
726   // sure the caret, which should be after any insertion, hasn't moved
727   // forward of the old selection start.)
728   const bool just_deleted_text =
729       (text_before_change_.length() > new_text.length()) &&
730       (new_sel.cp_min <= std::min(sel_before_change_.cp_min,
731                                  sel_before_change_.cp_max));
732 
733   delete_at_end_pressed_ = false;
734 
735   const bool something_changed = model_->OnAfterPossibleChange(
736       new_text, new_sel.selection_min(), new_sel.selection_max(),
737       selection_differs, text_changed_, just_deleted_text,
738       !IsImeComposing());
739 
740   // If only selection was changed, we don't need to call |controller_|'s
741   // OnChanged() method, which is called in TextChanged().
742   // But we still need to call EmphasizeURLComponents() to make sure the text
743   // attributes are updated correctly.
744   if (something_changed && text_changed_) {
745     TextChanged();
746   } else if (selection_differs) {
747     EmphasizeURLComponents();
748   } else if (delete_was_pressed_ && at_end_of_edit) {
749     delete_at_end_pressed_ = true;
750     model_->OnChanged();
751   }
752   delete_was_pressed_ = false;
753 
754   return something_changed;
755 }
756 
GetNativeView() const757 gfx::NativeView AutocompleteEditViewGtk::GetNativeView() const {
758   return alignment_.get();
759 }
760 
GetCommandUpdater()761 CommandUpdater* AutocompleteEditViewGtk::GetCommandUpdater() {
762   return command_updater_;
763 }
764 
SetInstantSuggestion(const string16 & suggestion,bool animate_to_complete)765 void AutocompleteEditViewGtk::SetInstantSuggestion(const string16& suggestion,
766                                                    bool animate_to_complete) {
767   std::string suggestion_utf8 = UTF16ToUTF8(suggestion);
768 
769   gtk_label_set_text(GTK_LABEL(instant_view_), suggestion_utf8.c_str());
770 
771   StopAnimation();
772 
773   if (suggestion.empty()) {
774     gtk_widget_hide(instant_view_);
775     return;
776   }
777   if (animate_to_complete
778 #if GTK_CHECK_VERSION(2, 20, 0)
779       && preedit_.empty()
780 #endif
781       ) {
782     instant_animation_->set_delegate(this);
783     instant_animation_->Start();
784   }
785 
786   gtk_widget_show(instant_view_);
787   AdjustVerticalAlignmentOfInstantView();
788   UpdateInstantViewColors();
789 }
790 
GetInstantSuggestion() const791 string16 AutocompleteEditViewGtk::GetInstantSuggestion() const {
792   const gchar* suggestion = gtk_label_get_text(GTK_LABEL(instant_view_));
793   return suggestion ? UTF8ToUTF16(suggestion) : string16();
794 }
795 
TextWidth() const796 int AutocompleteEditViewGtk::TextWidth() const {
797   // TextWidth may be called after gtk widget tree is destroyed but
798   // before AutocompleteEditViewGtk gets deleted.  This is a safe guard
799   // to avoid accessing |text_view_| that has already been destroyed.
800   // See crbug.com/70192.
801   if (!text_view_)
802     return 0;
803 
804   int horizontal_border_size =
805       gtk_text_view_get_border_window_size(GTK_TEXT_VIEW(text_view_),
806                                            GTK_TEXT_WINDOW_LEFT) +
807       gtk_text_view_get_border_window_size(GTK_TEXT_VIEW(text_view_),
808                                            GTK_TEXT_WINDOW_RIGHT) +
809       gtk_text_view_get_left_margin(GTK_TEXT_VIEW(text_view_)) +
810       gtk_text_view_get_right_margin(GTK_TEXT_VIEW(text_view_));
811 
812   GtkTextIter start, end;
813   GdkRectangle first_char_bounds, last_char_bounds;
814   gtk_text_buffer_get_start_iter(text_buffer_, &start);
815 
816   // Use the real end iterator here to take the width of instant suggestion
817   // text into account, so that location bar can layout its children correctly.
818   gtk_text_buffer_get_end_iter(text_buffer_, &end);
819   gtk_text_view_get_iter_location(GTK_TEXT_VIEW(text_view_),
820                                   &start, &first_char_bounds);
821   gtk_text_view_get_iter_location(GTK_TEXT_VIEW(text_view_),
822                                   &end, &last_char_bounds);
823 
824   gint first_char_start = first_char_bounds.x;
825   gint first_char_end = first_char_start + first_char_bounds.width;
826   gint last_char_start = last_char_bounds.x;
827   gint last_char_end = last_char_start + last_char_bounds.width;
828 
829   // bounds width could be negative for RTL text.
830   if (first_char_start > first_char_end)
831     std::swap(first_char_start, first_char_end);
832   if (last_char_start > last_char_end)
833     std::swap(last_char_start, last_char_end);
834 
835   gint text_width = first_char_start < last_char_start ?
836       last_char_end - first_char_start : first_char_end - last_char_start;
837 
838   return text_width + horizontal_border_size;
839 }
840 
IsImeComposing() const841 bool AutocompleteEditViewGtk::IsImeComposing() const {
842 #if GTK_CHECK_VERSION(2, 20, 0)
843   return !preedit_.empty();
844 #else
845   return false;
846 #endif
847 }
848 
849 #if defined(TOOLKIT_VIEWS)
AddToView(views::View * parent)850 views::View* AutocompleteEditViewGtk::AddToView(views::View* parent) {
851   views::NativeViewHost* host = new views::NativeViewHost;
852   parent->AddChildView(host);
853   host->set_focus_view(parent);
854   host->Attach(GetNativeView());
855   return host;
856 }
857 
OnPerformDrop(const views::DropTargetEvent & event)858 int AutocompleteEditViewGtk::OnPerformDrop(
859     const views::DropTargetEvent& event) {
860   string16 text;
861   const ui::OSExchangeData& data = event.data();
862   if (data.HasURL()) {
863     GURL url;
864     string16 title;
865     if (data.GetURLAndTitle(&url, &title))
866       text = UTF8ToUTF16(url.spec());
867   } else {
868     string16 data_string;
869     if (data.GetString(&data_string))
870       text = CollapseWhitespace(data_string, true);
871   }
872 
873   if (!text.empty() && OnPerformDropImpl(text))
874     return CopyOrLinkDragOperation(event.source_operations());
875 
876   return ui::DragDropTypes::DRAG_NONE;
877 }
878 
879 // static
Create(AutocompleteEditController * controller,ToolbarModel * toolbar_model,Profile * profile,CommandUpdater * command_updater,bool popup_window_mode,views::View * location_bar)880 AutocompleteEditView* AutocompleteEditViewGtk::Create(
881     AutocompleteEditController* controller,
882     ToolbarModel* toolbar_model,
883     Profile* profile,
884     CommandUpdater* command_updater,
885     bool popup_window_mode,
886     views::View* location_bar) {
887   if (views::NativeTextfieldViews::IsTextfieldViewsEnabled()) {
888     AutocompleteEditViewViews* autocomplete =
889         new AutocompleteEditViewViews(controller,
890                                       toolbar_model,
891                                       profile,
892                                       command_updater,
893                                       popup_window_mode,
894                                       location_bar);
895     autocomplete->Init();
896     return autocomplete;
897   }
898 
899   AutocompleteEditViewGtk* autocomplete =
900       new AutocompleteEditViewGtk(controller,
901                                   toolbar_model,
902                                   profile,
903                                   command_updater,
904                                   popup_window_mode,
905                                   location_bar);
906   autocomplete->Init();
907 
908   // Make all the children of the widget visible. NOTE: this won't display
909   // anything, it just toggles the visible flag.
910   gtk_widget_show_all(autocomplete->GetNativeView());
911   // Hide the widget. NativeViewHostGtk will make it visible again as
912   // necessary.
913   gtk_widget_hide(autocomplete->GetNativeView());
914 
915   return autocomplete;
916 }
917 #endif
918 
Observe(NotificationType type,const NotificationSource & source,const NotificationDetails & details)919 void AutocompleteEditViewGtk::Observe(NotificationType type,
920                                       const NotificationSource& source,
921                                       const NotificationDetails& details) {
922   DCHECK(type == NotificationType::BROWSER_THEME_CHANGED);
923 
924   SetBaseColor();
925 }
926 
AnimationEnded(const ui::Animation * animation)927 void AutocompleteEditViewGtk::AnimationEnded(const ui::Animation* animation) {
928   model_->CommitSuggestedText(false);
929 }
930 
AnimationProgressed(const ui::Animation * animation)931 void AutocompleteEditViewGtk::AnimationProgressed(
932     const ui::Animation* animation) {
933   UpdateInstantViewColors();
934 }
935 
AnimationCanceled(const ui::Animation * animation)936 void AutocompleteEditViewGtk::AnimationCanceled(
937     const ui::Animation* animation) {
938   UpdateInstantViewColors();
939 }
940 
SetBaseColor()941 void AutocompleteEditViewGtk::SetBaseColor() {
942   DCHECK(text_view_);
943 
944 #if defined(TOOLKIT_VIEWS)
945   bool use_gtk = false;
946 #else
947   bool use_gtk = theme_service_->UseGtkTheme();
948 #endif
949   if (use_gtk) {
950     gtk_widget_modify_cursor(text_view_, NULL, NULL);
951     gtk_widget_modify_base(text_view_, GTK_STATE_NORMAL, NULL);
952     gtk_widget_modify_base(text_view_, GTK_STATE_SELECTED, NULL);
953     gtk_widget_modify_text(text_view_, GTK_STATE_SELECTED, NULL);
954     gtk_widget_modify_base(text_view_, GTK_STATE_ACTIVE, NULL);
955     gtk_widget_modify_text(text_view_, GTK_STATE_ACTIVE, NULL);
956 
957     gtk_util::UndoForceFontSize(text_view_);
958     gtk_util::UndoForceFontSize(instant_view_);
959 
960     // Grab the text colors out of the style and set our tags to use them.
961     GtkStyle* style = gtk_rc_get_style(text_view_);
962 
963     // style may be unrealized at this point, so calculate the halfway point
964     // between text[] and base[] manually instead of just using text_aa[].
965     GdkColor average_color = gtk_util::AverageColors(
966         style->text[GTK_STATE_NORMAL], style->base[GTK_STATE_NORMAL]);
967 
968     g_object_set(faded_text_tag_, "foreground-gdk", &average_color, NULL);
969     g_object_set(normal_text_tag_, "foreground-gdk",
970                  &style->text[GTK_STATE_NORMAL], NULL);
971   } else {
972     const GdkColor* background_color_ptr;
973 #if defined(TOOLKIT_VIEWS)
974     const GdkColor background_color = gfx::SkColorToGdkColor(
975         LocationBarView::GetColor(ToolbarModel::NONE,
976                                   LocationBarView::BACKGROUND));
977     background_color_ptr = &background_color;
978 #else
979     background_color_ptr = &LocationBarViewGtk::kBackgroundColor;
980 #endif
981     gtk_widget_modify_cursor(
982         text_view_, &gtk_util::kGdkBlack, &gtk_util::kGdkGray);
983     gtk_widget_modify_base(text_view_, GTK_STATE_NORMAL, background_color_ptr);
984 
985 #if !defined(TOOLKIT_VIEWS)
986     GdkColor c;
987     // Override the selected colors so we don't leak colors from the current
988     // gtk theme into the chrome-theme.
989     c = gfx::SkColorToGdkColor(
990         theme_service_->get_active_selection_bg_color());
991     gtk_widget_modify_base(text_view_, GTK_STATE_SELECTED, &c);
992 
993     c = gfx::SkColorToGdkColor(
994         theme_service_->get_active_selection_fg_color());
995     gtk_widget_modify_text(text_view_, GTK_STATE_SELECTED, &c);
996 
997     c = gfx::SkColorToGdkColor(
998         theme_service_->get_inactive_selection_bg_color());
999     gtk_widget_modify_base(text_view_, GTK_STATE_ACTIVE, &c);
1000 
1001     c = gfx::SkColorToGdkColor(
1002         theme_service_->get_inactive_selection_fg_color());
1003     gtk_widget_modify_text(text_view_, GTK_STATE_ACTIVE, &c);
1004 #endif
1005 
1006     // Until we switch to vector graphics, force the font size.
1007     gtk_util::ForceFontSizePixels(text_view_, GetFont().GetFontSize());
1008     gtk_util::ForceFontSizePixels(instant_view_, GetFont().GetFontSize());
1009 
1010     g_object_set(faded_text_tag_, "foreground", kTextBaseColor, NULL);
1011     g_object_set(normal_text_tag_, "foreground", "#000000", NULL);
1012   }
1013 
1014   AdjustVerticalAlignmentOfInstantView();
1015   UpdateInstantViewColors();
1016 }
1017 
UpdateInstantViewColors()1018 void AutocompleteEditViewGtk::UpdateInstantViewColors() {
1019   SkColor selection_text, selection_bg;
1020   GdkColor faded_text, normal_bg;
1021 
1022 #if defined(TOOLKIT_VIEWS)
1023   bool use_gtk = false;
1024 #else
1025   bool use_gtk = theme_service_->UseGtkTheme();
1026 #endif
1027 
1028   if (use_gtk) {
1029     GtkStyle* style = gtk_rc_get_style(instant_view_);
1030 
1031     faded_text = gtk_util::AverageColors(
1032         style->text[GTK_STATE_NORMAL], style->base[GTK_STATE_NORMAL]);
1033     normal_bg = style->base[GTK_STATE_NORMAL];
1034 
1035     selection_text = gfx::GdkColorToSkColor(style->text[GTK_STATE_SELECTED]);
1036     selection_bg = gfx::GdkColorToSkColor(style->base[GTK_STATE_SELECTED]);
1037   } else {
1038     gdk_color_parse(kTextBaseColor, &faded_text);
1039 
1040 #if defined(TOOLKIT_VIEWS)
1041     normal_bg = gfx::SkColorToGdkColor(
1042         LocationBarView::GetColor(ToolbarModel::NONE,
1043                                   LocationBarView::BACKGROUND));
1044     selection_text = LocationBarView::GetColor(
1045         ToolbarModel::NONE, LocationBarView::SELECTED_TEXT);
1046 
1047     GtkStyle* style = gtk_rc_get_style(instant_view_);
1048     selection_bg = gfx::GdkColorToSkColor(style->base[GTK_STATE_SELECTED]);
1049 #else
1050     normal_bg = LocationBarViewGtk::kBackgroundColor;
1051     selection_text =
1052         theme_service_->get_active_selection_fg_color();
1053     selection_bg =
1054         theme_service_->get_active_selection_bg_color();
1055 #endif
1056   }
1057 
1058   double alpha = instant_animation_->is_animating() ?
1059       instant_animation_->GetCurrentValue() : 0.0;
1060   GdkColor text = gfx::SkColorToGdkColor(color_utils::AlphaBlend(
1061       selection_text,
1062       gfx::GdkColorToSkColor(faded_text),
1063       alpha * 0xff));
1064   GdkColor bg = gfx::SkColorToGdkColor(color_utils::AlphaBlend(
1065       selection_bg,
1066       gfx::GdkColorToSkColor(normal_bg),
1067       alpha * 0xff));
1068 
1069   if (alpha > 0.0) {
1070     gtk_label_select_region(GTK_LABEL(instant_view_), 0, -1);
1071     // ACTIVE is the state for text that is selected, but not focused.
1072     gtk_widget_modify_text(instant_view_, GTK_STATE_ACTIVE, &text);
1073     gtk_widget_modify_base(instant_view_, GTK_STATE_ACTIVE, &bg);
1074   } else {
1075     // When the text is unselected, fg is used for text color, the state
1076     // is NORMAL, and the background is transparent.
1077     gtk_widget_modify_fg(instant_view_, GTK_STATE_NORMAL, &text);
1078   }
1079 }
1080 
HandleBeginUserAction(GtkTextBuffer * sender)1081 void AutocompleteEditViewGtk::HandleBeginUserAction(GtkTextBuffer* sender) {
1082   OnBeforePossibleChange();
1083 }
1084 
HandleEndUserAction(GtkTextBuffer * sender)1085 void AutocompleteEditViewGtk::HandleEndUserAction(GtkTextBuffer* sender) {
1086   OnAfterPossibleChange();
1087 }
1088 
HandleKeyPress(GtkWidget * widget,GdkEventKey * event)1089 gboolean AutocompleteEditViewGtk::HandleKeyPress(GtkWidget* widget,
1090                                                  GdkEventKey* event) {
1091   // Background of this piece of complicated code:
1092   // The omnibox supports several special behaviors which may be triggered by
1093   // certain key events:
1094   // Tab to search - triggered by Tab key
1095   // Accept input - triggered by Enter key
1096   // Revert input - triggered by Escape key
1097   //
1098   // Because we use a GtkTextView object |text_view_| for text input, we need
1099   // send all key events to |text_view_| before handling them, to make sure
1100   // IME works without any problem. So here, we intercept "key-press-event"
1101   // signal of |text_view_| object and call its default handler to handle the
1102   // key event first.
1103   //
1104   // Then if the key event is one of Tab, Enter and Escape, we need to trigger
1105   // the corresponding special behavior if IME did not handle it.
1106   // For Escape key, if the default signal handler returns FALSE, then we know
1107   // it's not handled by IME.
1108   //
1109   // For Tab key, as "accepts-tab" property of |text_view_| is set to FALSE,
1110   // if IME did not handle it then "move-focus" signal will be emitted by the
1111   // default signal handler of |text_view_|. So we can intercept "move-focus"
1112   // signal of |text_view_| to know if a Tab key press event was handled by IME,
1113   // and trigger Tab to search behavior when necessary in the signal handler.
1114   //
1115   // But for Enter key, if IME did not handle the key event, the default signal
1116   // handler will delete current selection range and insert '\n' and always
1117   // return TRUE. We need to prevent |text_view_| from performing this default
1118   // action if IME did not handle the key event, because we don't want the
1119   // content of omnibox to be changed before triggering our special behavior.
1120   // Otherwise our special behavior would not be performed correctly.
1121   //
1122   // But there is no way for us to prevent GtkTextView from handling the key
1123   // event and performing built-in operation. So in order to achieve our goal,
1124   // "insert-text" signal of |text_buffer_| object is intercepted, and
1125   // following actions are done in the signal handler:
1126   // - If there is only one character in inserted text, and it's '\n' or '\r',
1127   //   then set |enter_was_inserted_| to true.
1128   // - Filter out all new line and tab characters.
1129   //
1130   // So if |enter_was_inserted_| is true after calling |text_view_|'s default
1131   // signal handler against an Enter key press event, then we know that the
1132   // Enter key press event was handled by GtkTextView rather than IME, and can
1133   // perform the special behavior for Enter key safely.
1134   //
1135   // Now the last thing is to prevent the content of omnibox from being changed
1136   // by GtkTextView when Enter key is pressed. As OnBeforePossibleChange() and
1137   // OnAfterPossibleChange() will be called by GtkTextView before and after
1138   // changing the content, and the content is already saved in
1139   // OnBeforePossibleChange(), so if the Enter key press event was not handled
1140   // by IME, it's easy to restore the content in OnAfterPossibleChange(), as if
1141   // it's not changed at all.
1142 
1143   GtkWidgetClass* klass = GTK_WIDGET_GET_CLASS(widget);
1144 
1145   enter_was_pressed_ = event->keyval == GDK_Return ||
1146                        event->keyval == GDK_ISO_Enter ||
1147                        event->keyval == GDK_KP_Enter;
1148 
1149   // Set |tab_was_pressed_| to true if it's a Tab key press event, so that our
1150   // handler of "move-focus" signal can trigger Tab to search behavior when
1151   // necessary.
1152   tab_was_pressed_ = (event->keyval == GDK_Tab ||
1153                       event->keyval == GDK_ISO_Left_Tab ||
1154                       event->keyval == GDK_KP_Tab) &&
1155                      !(event->state & (GDK_CONTROL_MASK | GDK_SHIFT_MASK));
1156 
1157   delete_was_pressed_ = event->keyval == GDK_Delete ||
1158                         event->keyval == GDK_KP_Delete;
1159 
1160   // Reset |enter_was_inserted_|, which may be set in the "insert-text" signal
1161   // handler, so that we'll know if an Enter key event was handled by IME.
1162   enter_was_inserted_ = false;
1163 
1164   // Reset |paste_clipboard_requested_| to make sure we won't misinterpret this
1165   // key input action as a paste action.
1166   paste_clipboard_requested_ = false;
1167 
1168   // Reset |text_changed_| before passing the key event on to the text view.
1169   text_changed_ = false;
1170 
1171   OnBeforePossibleChange();
1172   handling_key_press_ = true;
1173   content_maybe_changed_by_key_press_ = false;
1174 
1175   // Call the default handler, so that IME can work as normal.
1176   // New line characters will be filtered out by our "insert-text"
1177   // signal handler attached to |text_buffer_| object.
1178   gboolean result = klass->key_press_event(widget, event);
1179 
1180   handling_key_press_ = false;
1181   if (content_maybe_changed_by_key_press_)
1182     OnAfterPossibleChange();
1183 
1184   // Set |tab_was_pressed_| to false, to make sure Tab to search behavior can
1185   // only be triggered by pressing Tab key.
1186   tab_was_pressed_ = false;
1187 
1188   if (enter_was_pressed_ && enter_was_inserted_) {
1189     bool alt_held = (event->state & GDK_MOD1_MASK);
1190     model_->AcceptInput(alt_held ? NEW_FOREGROUND_TAB : CURRENT_TAB, false);
1191     result = TRUE;
1192   } else if (!result && event->keyval == GDK_Escape &&
1193              (event->state & gtk_accelerator_get_default_mod_mask()) == 0) {
1194     // We can handle the Escape key if |text_view_| did not handle it.
1195     // If it's not handled by us, then we need to propagate it up to the parent
1196     // widgets, so that Escape accelerator can still work.
1197     result = model_->OnEscapeKeyPressed();
1198   } else if (event->keyval == GDK_Control_L || event->keyval == GDK_Control_R) {
1199     // Omnibox2 can switch its contents while pressing a control key. To switch
1200     // the contents of omnibox2, we notify the AutocompleteEditModel class when
1201     // the control-key state is changed.
1202     model_->OnControlKeyChanged(true);
1203   } else if (!text_changed_ && event->keyval == GDK_Delete &&
1204              event->state & GDK_SHIFT_MASK) {
1205     // If shift+del didn't change the text, we let this delete an entry from
1206     // the popup.  We can't check to see if the IME handled it because even if
1207     // nothing is selected, the IME or the TextView still report handling it.
1208     if (model_->popup_model()->IsOpen())
1209       model_->popup_model()->TryDeletingCurrentItem();
1210   }
1211 
1212   // Set |enter_was_pressed_| to false, to make sure OnAfterPossibleChange() can
1213   // act as normal for changes made by other events.
1214   enter_was_pressed_ = false;
1215 
1216   // If the key event is not handled by |text_view_| or us, then we need to
1217   // propagate the key event up to parent widgets by returning FALSE.
1218   // In this case we need to stop the signal emission explicitly to prevent the
1219   // default "key-press-event" handler of |text_view_| from being called again.
1220   if (!result) {
1221     static guint signal_id =
1222         g_signal_lookup("key-press-event", GTK_TYPE_WIDGET);
1223     g_signal_stop_emission(widget, signal_id, 0);
1224   }
1225 
1226 #if defined(TOOLKIT_VIEWS)
1227   location_bar_view_->GetWidget()->NotifyAccessibilityEvent(
1228       location_bar_view_, ui::AccessibilityTypes::EVENT_TEXT_CHANGED, true);
1229 #endif
1230 
1231   return result;
1232 }
1233 
HandleKeyRelease(GtkWidget * widget,GdkEventKey * event)1234 gboolean AutocompleteEditViewGtk::HandleKeyRelease(GtkWidget* widget,
1235                                                    GdkEventKey* event) {
1236   // Omnibox2 can switch its contents while pressing a control key. To switch
1237   // the contents of omnibox2, we notify the AutocompleteEditModel class when
1238   // the control-key state is changed.
1239   if (event->keyval == GDK_Control_L || event->keyval == GDK_Control_R) {
1240     // Round trip to query the control state after the release.  This allows
1241     // you to release one control key while still holding another control key.
1242     GdkDisplay* display = gdk_drawable_get_display(event->window);
1243     GdkModifierType mod;
1244     gdk_display_get_pointer(display, NULL, NULL, NULL, &mod);
1245     if (!(mod & GDK_CONTROL_MASK))
1246       model_->OnControlKeyChanged(false);
1247   }
1248 
1249   // Even though we handled the press ourselves, let GtkTextView handle the
1250   // release.  It shouldn't do anything particularly interesting, but it will
1251   // handle the IME work for us.
1252   return FALSE;  // Propagate into GtkTextView.
1253 }
1254 
HandleViewButtonPress(GtkWidget * sender,GdkEventButton * event)1255 gboolean AutocompleteEditViewGtk::HandleViewButtonPress(GtkWidget* sender,
1256                                                         GdkEventButton* event) {
1257   // We don't need to care about double and triple clicks.
1258   if (event->type != GDK_BUTTON_PRESS)
1259     return FALSE;
1260 
1261   DCHECK(text_view_);
1262 
1263   if (event->button == 1) {
1264 #if defined(OS_CHROMEOS)
1265     // When the first button is pressed, track some stuff that will help us
1266     // determine whether we should select all of the text when the button is
1267     // released.
1268     button_1_pressed_ = true;
1269     text_view_focused_before_button_press_ = GTK_WIDGET_HAS_FOCUS(text_view_);
1270     text_selected_during_click_ = false;
1271 #endif
1272 
1273     // Button press event may change the selection, we need to record the change
1274     // and report it to |model_| later when button is released.
1275     OnBeforePossibleChange();
1276   } else if (event->button == 2) {
1277     // GtkTextView pastes PRIMARY selection with middle click.
1278     // We can't call model_->on_paste_replacing_all() here, because the actual
1279     // paste clipboard action may not be performed if the clipboard is empty.
1280     paste_clipboard_requested_ = true;
1281   }
1282   return FALSE;
1283 }
1284 
HandleViewButtonRelease(GtkWidget * sender,GdkEventButton * event)1285 gboolean AutocompleteEditViewGtk::HandleViewButtonRelease(
1286     GtkWidget* sender, GdkEventButton* event) {
1287   if (event->button != 1)
1288     return FALSE;
1289 
1290   DCHECK(text_view_);
1291 
1292 #if defined(OS_CHROMEOS)
1293   button_1_pressed_ = false;
1294 #endif
1295 
1296   // Call the GtkTextView default handler, ignoring the fact that it will
1297   // likely have told us to stop propagating.  We want to handle selection.
1298   GtkWidgetClass* klass = GTK_WIDGET_GET_CLASS(text_view_);
1299   klass->button_release_event(text_view_, event);
1300 
1301 #if defined(OS_CHROMEOS)
1302   if (!text_view_focused_before_button_press_ && !text_selected_during_click_) {
1303     // If this was a focusing click and the user didn't drag to highlight any
1304     // text, select the full input and update the PRIMARY selection.
1305     SelectAllInternal(false, true);
1306 
1307     // So we told the buffer where the cursor should be, but make sure to tell
1308     // the view so it can scroll it to be visible if needed.
1309     // NOTE: This function doesn't seem to like a count of 0, looking at the
1310     // code it will skip an important loop.  Use -1 to achieve the same.
1311     GtkTextIter start, end;
1312     GetTextBufferBounds(&start, &end);
1313     gtk_text_view_move_visually(GTK_TEXT_VIEW(text_view_), &start, -1);
1314   }
1315 #endif
1316 
1317   // Inform |model_| about possible text selection change.
1318   OnAfterPossibleChange();
1319 
1320   return TRUE;  // Don't continue, we called the default handler already.
1321 }
1322 
HandleViewFocusIn(GtkWidget * sender,GdkEventFocus * event)1323 gboolean AutocompleteEditViewGtk::HandleViewFocusIn(GtkWidget* sender,
1324                                                     GdkEventFocus* event) {
1325   DCHECK(text_view_);
1326   update_popup_without_focus_ = false;
1327 
1328   GdkModifierType modifiers;
1329   gdk_window_get_pointer(text_view_->window, NULL, NULL, &modifiers);
1330   model_->OnSetFocus((modifiers & GDK_CONTROL_MASK) != 0);
1331   controller_->OnSetFocus();
1332   // TODO(deanm): Some keyword hit business, etc here.
1333 
1334   g_signal_connect(
1335       gdk_keymap_get_for_display(gtk_widget_get_display(text_view_)),
1336       "direction-changed",
1337       G_CALLBACK(&HandleKeymapDirectionChangedThunk), this);
1338 
1339   AdjustTextJustification();
1340 
1341   return FALSE;  // Continue propagation.
1342 }
1343 
HandleViewFocusOut(GtkWidget * sender,GdkEventFocus * event)1344 gboolean AutocompleteEditViewGtk::HandleViewFocusOut(GtkWidget* sender,
1345                                                      GdkEventFocus* event) {
1346   DCHECK(text_view_);
1347   GtkWidget* view_getting_focus = NULL;
1348   GtkWindow* toplevel = platform_util::GetTopLevel(sender);
1349   if (gtk_window_is_active(toplevel))
1350     view_getting_focus = going_to_focus_;
1351 
1352   // This must be invoked before ClosePopup.
1353   model_->OnWillKillFocus(view_getting_focus);
1354 
1355   // Close the popup.
1356   ClosePopup();
1357   // Tell the model to reset itself.
1358   model_->OnKillFocus();
1359   controller_->OnKillFocus();
1360 
1361   g_signal_handlers_disconnect_by_func(
1362       gdk_keymap_get_for_display(gtk_widget_get_display(text_view_)),
1363       reinterpret_cast<gpointer>(&HandleKeymapDirectionChangedThunk), this);
1364 
1365   return FALSE;  // Pass the event on to the GtkTextView.
1366 }
1367 
HandleViewMoveCursor(GtkWidget * sender,GtkMovementStep step,gint count,gboolean extend_selection)1368 void AutocompleteEditViewGtk::HandleViewMoveCursor(
1369     GtkWidget* sender,
1370     GtkMovementStep step,
1371     gint count,
1372     gboolean extend_selection) {
1373   DCHECK(text_view_);
1374   GtkTextIter sel_start, sel_end;
1375   gboolean has_selection =
1376       gtk_text_buffer_get_selection_bounds(text_buffer_, &sel_start, &sel_end);
1377   bool handled = false;
1378 
1379   if (step == GTK_MOVEMENT_VISUAL_POSITIONS && !extend_selection &&
1380       (count == 1 || count == -1)) {
1381     // We need to take the content direction into account when handling cursor
1382     // movement, because the behavior of Left and Right key will be inverted if
1383     // the direction is RTL. Although we should check the direction around the
1384     // input caret, it's much simpler and good enough to check whole content's
1385     // direction.
1386     PangoDirection content_dir = GetContentDirection();
1387     gint count_towards_end = content_dir == PANGO_DIRECTION_RTL ? -1 : 1;
1388 
1389     // We want the GtkEntry behavior when you move the cursor while you have a
1390     // selection.  GtkTextView just drops the selection and moves the cursor,
1391     // but instead we want to move the cursor to the appropiate end of the
1392     // selection.
1393     if (has_selection) {
1394       // We have a selection and start / end are in ascending order.
1395       // Cursor placement will remove the selection, so we need inform
1396       // |model_| about this change by
1397       // calling On{Before|After}PossibleChange() methods.
1398       OnBeforePossibleChange();
1399       gtk_text_buffer_place_cursor(
1400           text_buffer_, count == count_towards_end ? &sel_end : &sel_start);
1401       OnAfterPossibleChange();
1402       handled = true;
1403     } else if (count == count_towards_end && !IsCaretAtEnd()) {
1404       handled = model_->CommitSuggestedText(true);
1405     }
1406   } else if (step == GTK_MOVEMENT_PAGES) {  // Page up and down.
1407     // Multiply by count for the direction (if we move too much that's ok).
1408     model_->OnUpOrDownKeyPressed(model_->result().size() * count);
1409     handled = true;
1410   } else if (step == GTK_MOVEMENT_DISPLAY_LINES) {  // Arrow up and down.
1411     model_->OnUpOrDownKeyPressed(count);
1412     handled = true;
1413   }
1414 
1415   if (!handled) {
1416     // Cursor movement may change the selection, we need to record the change
1417     // and report it to |model_|.
1418     if (has_selection || extend_selection)
1419       OnBeforePossibleChange();
1420 
1421     // Propagate into GtkTextView
1422     GtkTextViewClass* klass = GTK_TEXT_VIEW_GET_CLASS(text_view_);
1423     klass->move_cursor(GTK_TEXT_VIEW(text_view_), step, count,
1424                        extend_selection);
1425 
1426     if (has_selection || extend_selection)
1427       OnAfterPossibleChange();
1428   }
1429 
1430   // move-cursor doesn't use a signal accumulator on the return value (it
1431   // just ignores then), so we have to stop the propagation.
1432   static guint signal_id = g_signal_lookup("move-cursor", GTK_TYPE_TEXT_VIEW);
1433   g_signal_stop_emission(text_view_, signal_id, 0);
1434 }
1435 
HandleViewSizeRequest(GtkWidget * sender,GtkRequisition * req)1436 void AutocompleteEditViewGtk::HandleViewSizeRequest(GtkWidget* sender,
1437                                                     GtkRequisition* req) {
1438   // Don't force a minimum width, but use the font-relative height.  This is a
1439   // run-first handler, so the default handler was already called.
1440   req->width = 1;
1441 }
1442 
HandlePopupMenuDeactivate(GtkWidget * sender)1443 void AutocompleteEditViewGtk::HandlePopupMenuDeactivate(GtkWidget* sender) {
1444   // When the context menu appears, |text_view_|'s focus is lost. After an item
1445   // is activated, the focus comes back to |text_view_|, but only after the
1446   // check in UpdatePopup(). We set this flag to make UpdatePopup() aware that
1447   // it will be receiving focus again.
1448   if (!model_->has_focus())
1449     update_popup_without_focus_ = true;
1450 }
1451 
HandlePopulatePopup(GtkWidget * sender,GtkMenu * menu)1452 void AutocompleteEditViewGtk::HandlePopulatePopup(GtkWidget* sender,
1453                                                   GtkMenu* menu) {
1454   GtkWidget* separator = gtk_separator_menu_item_new();
1455   gtk_menu_shell_append(GTK_MENU_SHELL(menu), separator);
1456   gtk_widget_show(separator);
1457 
1458   // Search Engine menu item.
1459   GtkWidget* search_engine_menuitem = gtk_menu_item_new_with_mnemonic(
1460       gfx::ConvertAcceleratorsFromWindowsStyle(
1461           l10n_util::GetStringUTF8(IDS_EDIT_SEARCH_ENGINES)).c_str());
1462   gtk_menu_shell_append(GTK_MENU_SHELL(menu), search_engine_menuitem);
1463   g_signal_connect(search_engine_menuitem, "activate",
1464                    G_CALLBACK(HandleEditSearchEnginesThunk), this);
1465   gtk_widget_set_sensitive(search_engine_menuitem,
1466       command_updater_->IsCommandEnabled(IDC_EDIT_SEARCH_ENGINES));
1467   gtk_widget_show(search_engine_menuitem);
1468 
1469   // We need to update the paste and go controller before we know what text
1470   // to show. We could do this all asynchronously, but it would be elaborate
1471   // because we'd have to account for multiple menus showing, getting called
1472   // back after shutdown, and similar issues.
1473   GtkClipboard* x_clipboard = gtk_clipboard_get(GDK_SELECTION_CLIPBOARD);
1474   gchar* text = gtk_clipboard_wait_for_text(x_clipboard);
1475   string16 text_wstr = UTF8ToUTF16(text ? text : "");
1476   g_free(text);
1477 
1478   // Paste and Go menu item.
1479   GtkWidget* paste_go_menuitem = gtk_menu_item_new_with_mnemonic(
1480       gfx::ConvertAcceleratorsFromWindowsStyle(
1481           l10n_util::GetStringUTF8(model_->is_paste_and_search() ?
1482               IDS_PASTE_AND_SEARCH : IDS_PASTE_AND_GO)).c_str());
1483   gtk_menu_shell_append(GTK_MENU_SHELL(menu), paste_go_menuitem);
1484   g_signal_connect(paste_go_menuitem, "activate",
1485                    G_CALLBACK(HandlePasteAndGoThunk), this);
1486   gtk_widget_set_sensitive(paste_go_menuitem,
1487                            model_->CanPasteAndGo(text_wstr));
1488   gtk_widget_show(paste_go_menuitem);
1489 
1490   g_signal_connect(menu, "deactivate",
1491                    G_CALLBACK(HandlePopupMenuDeactivateThunk), this);
1492 }
1493 
HandleEditSearchEngines(GtkWidget * sender)1494 void AutocompleteEditViewGtk::HandleEditSearchEngines(GtkWidget* sender) {
1495   command_updater_->ExecuteCommand(IDC_EDIT_SEARCH_ENGINES);
1496 }
1497 
HandlePasteAndGo(GtkWidget * sender)1498 void AutocompleteEditViewGtk::HandlePasteAndGo(GtkWidget* sender) {
1499   model_->PasteAndGo();
1500 }
1501 
HandleMarkSet(GtkTextBuffer * buffer,GtkTextIter * location,GtkTextMark * mark)1502 void AutocompleteEditViewGtk::HandleMarkSet(GtkTextBuffer* buffer,
1503                                             GtkTextIter* location,
1504                                             GtkTextMark* mark) {
1505   if (!text_buffer_ || buffer != text_buffer_)
1506     return;
1507 
1508   if (mark != gtk_text_buffer_get_insert(text_buffer_) &&
1509       mark != gtk_text_buffer_get_selection_bound(text_buffer_)) {
1510     return;
1511   }
1512 
1513   StopAnimation();
1514 
1515   // If we are here, that means the user may be changing the selection
1516   selection_suggested_ = false;
1517 
1518   // Get the currently-selected text, if there is any.
1519   std::string new_selected_text = GetSelectedText();
1520 
1521 #if defined(OS_CHROMEOS)
1522   // If the user just selected some text with the mouse (or at least while the
1523   // mouse button was down), make sure that we won't blow their selection away
1524   // later by selecting all of the text when the button is released.
1525   if (button_1_pressed_ && !new_selected_text.empty())
1526     text_selected_during_click_ = true;
1527 #endif
1528 
1529   // If we had some text selected earlier but it's no longer highlighted, we
1530   // might need to save it now...
1531   if (!selected_text_.empty() && new_selected_text.empty()) {
1532     // ... but only if we currently own the selection.  We want to manually
1533     // update the selection when the text is unhighlighted because the user
1534     // clicked in a blank area of the text view, but not when it's unhighlighted
1535     // because another client or widget took the selection.  (This handler gets
1536     // called before the default handler, so as long as nobody else took the
1537     // selection, the text buffer still owns it even if GTK is about to take it
1538     // away in the default handler.)
1539     GtkClipboard* clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
1540     if (gtk_clipboard_get_owner(clipboard) == G_OBJECT(text_buffer_))
1541       SavePrimarySelection(selected_text_);
1542   }
1543 
1544   selected_text_ = new_selected_text;
1545 }
1546 
1547 // Override the primary selection the text buffer has set. This has to happen
1548 // after the default handler for the "mark-set" signal.
HandleMarkSetAfter(GtkTextBuffer * buffer,GtkTextIter * location,GtkTextMark * mark)1549 void AutocompleteEditViewGtk::HandleMarkSetAfter(GtkTextBuffer* buffer,
1550                                                  GtkTextIter* location,
1551                                                  GtkTextMark* mark) {
1552   if (!text_buffer_ || buffer != text_buffer_)
1553     return;
1554 
1555   // We should only update primary selection when the user changes the selection
1556   // range.
1557   if (mark != gtk_text_buffer_get_insert(text_buffer_) &&
1558       mark != gtk_text_buffer_get_selection_bound(text_buffer_)) {
1559     return;
1560   }
1561 
1562   UpdatePrimarySelectionIfValidURL();
1563 }
1564 
1565 // Just use the default behavior for DnD, except if the drop can be a PasteAndGo
1566 // then override.
HandleDragDataReceived(GtkWidget * sender,GdkDragContext * context,gint x,gint y,GtkSelectionData * selection_data,guint target_type,guint time)1567 void AutocompleteEditViewGtk::HandleDragDataReceived(
1568     GtkWidget* sender, GdkDragContext* context, gint x, gint y,
1569     GtkSelectionData* selection_data, guint target_type, guint time) {
1570   DCHECK(text_view_);
1571 
1572   // Reset |paste_clipboard_requested_| to make sure we won't misinterpret this
1573   // drop action as a paste action.
1574   paste_clipboard_requested_ = false;
1575 
1576   // Don't try to PasteAndGo on drops originating from this omnibox. However, do
1577   // allow default behavior for such drags.
1578   if (context->source_window == text_view_->window)
1579     return;
1580 
1581   guchar* text = gtk_selection_data_get_text(selection_data);
1582   if (!text)
1583     return;
1584 
1585   string16 possible_url = UTF8ToUTF16(reinterpret_cast<char*>(text));
1586   g_free(text);
1587   if (OnPerformDropImpl(possible_url)) {
1588     gtk_drag_finish(context, TRUE, TRUE, time);
1589 
1590     static guint signal_id =
1591         g_signal_lookup("drag-data-received", GTK_TYPE_WIDGET);
1592     g_signal_stop_emission(text_view_, signal_id, 0);
1593   }
1594 }
1595 
HandleDragDataGet(GtkWidget * widget,GdkDragContext * context,GtkSelectionData * selection_data,guint target_type,guint time)1596 void AutocompleteEditViewGtk::HandleDragDataGet(
1597     GtkWidget* widget,
1598     GdkDragContext* context,
1599     GtkSelectionData* selection_data,
1600     guint target_type,
1601     guint time) {
1602   DCHECK(text_view_);
1603   // If GTK put the normal textual version of the selection in our drag data,
1604   // put our doctored selection that might have the 'http://' prefix. Also, GTK
1605   // is confused about signedness of its datatypes, leading to the weird switch
1606   // statement (no set of casts fixes this).
1607   switch (target_type) {
1608     case GTK_TEXT_BUFFER_TARGET_INFO_TEXT: {
1609       gtk_selection_data_set_text(selection_data, selected_text_.c_str(), -1);
1610     }
1611   }
1612 }
1613 
HandleInsertText(GtkTextBuffer * buffer,GtkTextIter * location,const gchar * text,gint len)1614 void AutocompleteEditViewGtk::HandleInsertText(
1615     GtkTextBuffer* buffer, GtkTextIter* location, const gchar* text, gint len) {
1616   std::string filtered_text;
1617   filtered_text.reserve(len);
1618 
1619   // Filter out new line and tab characters.
1620   // |text| is guaranteed to be a valid UTF-8 string, so we don't need to
1621   // validate it here.
1622   //
1623   // If there was only a single character, then it might be generated by a key
1624   // event. In this case, we save the single character to help our
1625   // "key-press-event" signal handler distinguish if an Enter key event is
1626   // handled by IME or not.
1627   if (len == 1 && (text[0] == '\n' || text[0] == '\r'))
1628     enter_was_inserted_ = true;
1629 
1630   const gchar* p = text;
1631   while (*p && (p - text) < len) {
1632     gunichar c = g_utf8_get_char(p);
1633     const gchar* next = g_utf8_next_char(p);
1634 
1635     // 0x200B is Zero Width Space, which is inserted just before the instant
1636     // anchor for working around the GtkTextView's misalignment bug.
1637     // This character might be captured and inserted into the content by undo
1638     // manager, so we need to filter it out here.
1639     if (c != L'\n' && c != L'\r' && c != L'\t' && c != 0x200B)
1640       filtered_text.append(p, next);
1641 
1642     p = next;
1643   }
1644 
1645   if (filtered_text.length()) {
1646     // Avoid inserting the text after the instant anchor.
1647     ValidateTextBufferIter(location);
1648 
1649     // Call the default handler to insert filtered text.
1650     GtkTextBufferClass* klass = GTK_TEXT_BUFFER_GET_CLASS(buffer);
1651     klass->insert_text(buffer, location, filtered_text.data(),
1652                        static_cast<gint>(filtered_text.length()));
1653   }
1654 
1655   // Stop propagating the signal emission to prevent the default handler from
1656   // being called again.
1657   static guint signal_id = g_signal_lookup("insert-text", GTK_TYPE_TEXT_BUFFER);
1658   g_signal_stop_emission(buffer, signal_id, 0);
1659 }
1660 
HandleBackSpace(GtkWidget * sender)1661 void AutocompleteEditViewGtk::HandleBackSpace(GtkWidget* sender) {
1662   // Checks if it's currently in keyword search mode.
1663   if (model_->is_keyword_hint() || model_->keyword().empty())
1664     return;  // Propgate into GtkTextView.
1665 
1666   DCHECK(text_view_);
1667 
1668   GtkTextIter sel_start, sel_end;
1669   // Checks if there is some text selected.
1670   if (gtk_text_buffer_get_selection_bounds(text_buffer_, &sel_start, &sel_end))
1671     return;  // Propgate into GtkTextView.
1672 
1673   GtkTextIter start;
1674   gtk_text_buffer_get_start_iter(text_buffer_, &start);
1675 
1676   if (!gtk_text_iter_equal(&start, &sel_start))
1677     return;  // Propgate into GtkTextView.
1678 
1679   // We're showing a keyword and the user pressed backspace at the beginning
1680   // of the text. Delete the selected keyword.
1681   model_->ClearKeyword(GetText());
1682 
1683   // Stop propagating the signal emission into GtkTextView.
1684   static guint signal_id = g_signal_lookup("backspace", GTK_TYPE_TEXT_VIEW);
1685   g_signal_stop_emission(text_view_, signal_id, 0);
1686 }
1687 
HandleViewMoveFocus(GtkWidget * widget,GtkDirectionType direction)1688 void AutocompleteEditViewGtk::HandleViewMoveFocus(GtkWidget* widget,
1689                                                   GtkDirectionType direction) {
1690   if (!tab_was_pressed_)
1691     return;
1692 
1693   // If special behavior is triggered, then stop the signal emission to
1694   // prevent the focus from being moved.
1695   bool handled = false;
1696 
1697   // Trigger Tab to search behavior only when Tab key is pressed.
1698   if (model_->is_keyword_hint())
1699     handled = model_->AcceptKeyword();
1700 
1701 #if GTK_CHECK_VERSION(2, 20, 0)
1702   if (!handled && !preedit_.empty())
1703     handled = true;
1704 #endif
1705 
1706   if (!handled && GTK_WIDGET_VISIBLE(instant_view_))
1707     handled = model_->CommitSuggestedText(true);
1708 
1709   if (!handled) {
1710     if (!IsCaretAtEnd()) {
1711       OnBeforePossibleChange();
1712       PlaceCaretAt(GetTextLength());
1713       OnAfterPossibleChange();
1714       handled = true;
1715     }
1716   }
1717 
1718   if (!handled)
1719     handled = model_->AcceptCurrentInstantPreview();
1720 
1721   if (handled) {
1722     static guint signal_id = g_signal_lookup("move-focus", GTK_TYPE_WIDGET);
1723     g_signal_stop_emission(widget, signal_id, 0);
1724   }
1725 }
1726 
HandleCopyClipboard(GtkWidget * sender)1727 void AutocompleteEditViewGtk::HandleCopyClipboard(GtkWidget* sender) {
1728   HandleCopyOrCutClipboard(true);
1729 }
1730 
HandleCutClipboard(GtkWidget * sender)1731 void AutocompleteEditViewGtk::HandleCutClipboard(GtkWidget* sender) {
1732   HandleCopyOrCutClipboard(false);
1733 }
1734 
HandleCopyOrCutClipboard(bool copy)1735 void AutocompleteEditViewGtk::HandleCopyOrCutClipboard(bool copy) {
1736   DCHECK(text_view_);
1737 
1738   // On copy or cut, we manually update the PRIMARY selection to contain the
1739   // highlighted text.  This matches Firefox -- we highlight the URL but don't
1740   // update PRIMARY on Ctrl-L, so Ctrl-L, Ctrl-C and then middle-click is a
1741   // convenient way to paste the current URL somewhere.
1742   if (!gtk_text_buffer_get_has_selection(text_buffer_))
1743     return;
1744 
1745   GtkClipboard* clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
1746   DCHECK(clipboard);
1747   if (!clipboard)
1748     return;
1749 
1750   CharRange selection = GetSelection();
1751   GURL url;
1752   string16 text(UTF8ToUTF16(GetSelectedText()));
1753   bool write_url;
1754   model_->AdjustTextForCopy(selection.selection_min(), IsSelectAll(), &text,
1755                             &url, &write_url);
1756 
1757   if (write_url) {
1758     BookmarkNodeData data;
1759     data.ReadFromTuple(url, text);
1760     data.WriteToClipboard(NULL);
1761 
1762     // Stop propagating the signal.
1763     static guint copy_signal_id =
1764         g_signal_lookup("copy-clipboard", GTK_TYPE_TEXT_VIEW);
1765     static guint cut_signal_id =
1766         g_signal_lookup("cut-clipboard", GTK_TYPE_TEXT_VIEW);
1767     g_signal_stop_emission(text_view_,
1768                            copy ? copy_signal_id : cut_signal_id,
1769                            0);
1770 
1771     if (!copy && gtk_text_view_get_editable(GTK_TEXT_VIEW(text_view_)))
1772       gtk_text_buffer_delete_selection(text_buffer_, true, true);
1773   }
1774 
1775   OwnPrimarySelection(UTF16ToUTF8(text));
1776 }
1777 
OnPerformDropImpl(const string16 & text)1778 bool AutocompleteEditViewGtk::OnPerformDropImpl(const string16& text) {
1779   if (model_->CanPasteAndGo(CollapseWhitespace(text, true))) {
1780     model_->PasteAndGo();
1781     return true;
1782   }
1783 
1784   return false;
1785 }
1786 
GetFont()1787 gfx::Font AutocompleteEditViewGtk::GetFont() {
1788 #if defined(TOOLKIT_VIEWS)
1789   bool use_gtk = false;
1790 #else
1791   bool use_gtk = theme_service_->UseGtkTheme();
1792 #endif
1793 
1794   if (use_gtk) {
1795     // If we haven't initialized the text view yet, just create a temporary one
1796     // whose style we can grab.
1797     GtkWidget* widget = text_view_ ? text_view_ : gtk_text_view_new();
1798     GtkRcStyle* rc_style = gtk_widget_get_modifier_style(widget);
1799     gfx::Font font((rc_style && rc_style->font_desc) ?
1800                    rc_style->font_desc :
1801                    widget->style->font_desc);
1802     if (!text_view_)
1803       g_object_unref(g_object_ref_sink(widget));
1804 
1805     // Scaling the font down for popup windows doesn't help here, since we just
1806     // use the normal unforced font size when using the GTK theme.
1807     return font;
1808   } else {
1809     return gfx::Font(
1810         ResourceBundle::GetSharedInstance().GetFont(ResourceBundle::BaseFont).
1811             GetFontName(),
1812         popup_window_mode_ ?
1813             browser_defaults::kAutocompleteEditFontPixelSizeInPopup :
1814             browser_defaults::kAutocompleteEditFontPixelSize);
1815   }
1816 }
1817 
OwnPrimarySelection(const std::string & text)1818 void AutocompleteEditViewGtk::OwnPrimarySelection(const std::string& text) {
1819   primary_selection_text_ = text;
1820 
1821   GtkTargetList* list = gtk_target_list_new(NULL, 0);
1822   gtk_target_list_add_text_targets(list, 0);
1823   gint len;
1824   GtkTargetEntry* entries = gtk_target_table_new_from_list(list, &len);
1825 
1826   // When |text_buffer_| is destroyed, it will clear the clipboard, hence
1827   // we needn't worry about calling gtk_clipboard_clear().
1828   gtk_clipboard_set_with_owner(gtk_clipboard_get(GDK_SELECTION_PRIMARY),
1829                                entries, len,
1830                                ClipboardGetSelectionThunk,
1831                                ClipboardSelectionCleared,
1832                                G_OBJECT(text_buffer_));
1833 
1834   gtk_target_list_unref(list);
1835   gtk_target_table_free(entries, len);
1836 }
1837 
HandlePasteClipboard(GtkWidget * sender)1838 void AutocompleteEditViewGtk::HandlePasteClipboard(GtkWidget* sender) {
1839   // We can't call model_->on_paste_replacing_all() here, because the actual
1840   // paste clipboard action may not be performed if the clipboard is empty.
1841   paste_clipboard_requested_ = true;
1842 }
1843 
WindowBoundsFromIters(GtkTextIter * iter1,GtkTextIter * iter2)1844 gfx::Rect AutocompleteEditViewGtk::WindowBoundsFromIters(
1845     GtkTextIter* iter1, GtkTextIter* iter2) {
1846   GdkRectangle start_location, end_location;
1847   GtkTextView* text_view = GTK_TEXT_VIEW(text_view_);
1848   gtk_text_view_get_iter_location(text_view, iter1, &start_location);
1849   gtk_text_view_get_iter_location(text_view, iter2, &end_location);
1850 
1851   gint x1, x2, y1, y2;
1852   gtk_text_view_buffer_to_window_coords(text_view, GTK_TEXT_WINDOW_WIDGET,
1853                                         start_location.x, start_location.y,
1854                                         &x1, &y1);
1855   gtk_text_view_buffer_to_window_coords(text_view, GTK_TEXT_WINDOW_WIDGET,
1856                                         end_location.x + end_location.width,
1857                                         end_location.y + end_location.height,
1858                                         &x2, &y2);
1859 
1860   return gfx::Rect(x1, y1, x2 - x1, y2 - y1);
1861 }
1862 
HandleExposeEvent(GtkWidget * sender,GdkEventExpose * expose)1863 gboolean AutocompleteEditViewGtk::HandleExposeEvent(GtkWidget* sender,
1864                                                     GdkEventExpose* expose) {
1865   if (strikethrough_.cp_min >= strikethrough_.cp_max)
1866     return FALSE;
1867   DCHECK(text_view_);
1868 
1869   gfx::Rect expose_rect(expose->area);
1870 
1871   GtkTextIter iter_min, iter_max;
1872   ItersFromCharRange(strikethrough_, &iter_min, &iter_max);
1873   gfx::Rect strikethrough_rect = WindowBoundsFromIters(&iter_min, &iter_max);
1874 
1875   if (!expose_rect.Intersects(strikethrough_rect))
1876     return FALSE;
1877 
1878   // Finally, draw.
1879   cairo_t* cr = gdk_cairo_create(GDK_DRAWABLE(expose->window));
1880   cairo_rectangle(cr, expose_rect.x(), expose_rect.y(),
1881                       expose_rect.width(), expose_rect.height());
1882   cairo_clip(cr);
1883 
1884   // TODO(estade): we probably shouldn't draw the strikethrough on selected
1885   // text. I started to do this, but it was way more effort than it seemed
1886   // worth.
1887   strikethrough_rect.Inset(kStrikethroughStrokeWidth,
1888                            kStrikethroughStrokeWidth);
1889   cairo_set_source_rgb(cr, kStrikethroughStrokeRed, 0.0, 0.0);
1890   cairo_set_line_width(cr, kStrikethroughStrokeWidth);
1891   cairo_set_line_cap(cr, CAIRO_LINE_CAP_ROUND);
1892   cairo_move_to(cr, strikethrough_rect.x(), strikethrough_rect.bottom());
1893   cairo_line_to(cr, strikethrough_rect.right(), strikethrough_rect.y());
1894   cairo_stroke(cr);
1895   cairo_destroy(cr);
1896 
1897   return FALSE;
1898 }
1899 
SelectAllInternal(bool reversed,bool update_primary_selection)1900 void AutocompleteEditViewGtk::SelectAllInternal(bool reversed,
1901                                                 bool update_primary_selection) {
1902   GtkTextIter start, end;
1903   if (reversed) {
1904     GetTextBufferBounds(&end, &start);
1905   } else {
1906     GetTextBufferBounds(&start, &end);
1907   }
1908   if (!update_primary_selection)
1909     StartUpdatingHighlightedText();
1910   gtk_text_buffer_select_range(text_buffer_, &start, &end);
1911   if (!update_primary_selection)
1912     FinishUpdatingHighlightedText();
1913 }
1914 
StartUpdatingHighlightedText()1915 void AutocompleteEditViewGtk::StartUpdatingHighlightedText() {
1916   if (GTK_WIDGET_REALIZED(text_view_)) {
1917     GtkClipboard* clipboard =
1918         gtk_widget_get_clipboard(text_view_, GDK_SELECTION_PRIMARY);
1919     DCHECK(clipboard);
1920     if (clipboard)
1921       gtk_text_buffer_remove_selection_clipboard(text_buffer_, clipboard);
1922   }
1923   g_signal_handler_block(text_buffer_, mark_set_handler_id_);
1924   g_signal_handler_block(text_buffer_, mark_set_handler_id2_);
1925 }
1926 
FinishUpdatingHighlightedText()1927 void AutocompleteEditViewGtk::FinishUpdatingHighlightedText() {
1928   if (GTK_WIDGET_REALIZED(text_view_)) {
1929     GtkClipboard* clipboard =
1930         gtk_widget_get_clipboard(text_view_, GDK_SELECTION_PRIMARY);
1931     DCHECK(clipboard);
1932     if (clipboard)
1933       gtk_text_buffer_add_selection_clipboard(text_buffer_, clipboard);
1934   }
1935   g_signal_handler_unblock(text_buffer_, mark_set_handler_id_);
1936   g_signal_handler_unblock(text_buffer_, mark_set_handler_id2_);
1937 }
1938 
1939 AutocompleteEditViewGtk::CharRange
GetSelection() const1940   AutocompleteEditViewGtk::GetSelection() const {
1941   // You can not just use get_selection_bounds here, since the order will be
1942   // ascending, and you don't know where the user's start and end of the
1943   // selection was (if the selection was forwards or backwards).  Get the
1944   // actual marks so that we can preserve the selection direction.
1945   GtkTextIter start, insert;
1946   GtkTextMark* mark;
1947 
1948   mark = gtk_text_buffer_get_selection_bound(text_buffer_);
1949   gtk_text_buffer_get_iter_at_mark(text_buffer_, &start, mark);
1950 
1951   mark = gtk_text_buffer_get_insert(text_buffer_);
1952   gtk_text_buffer_get_iter_at_mark(text_buffer_, &insert, mark);
1953 
1954   gint start_offset = gtk_text_iter_get_offset(&start);
1955   gint end_offset = gtk_text_iter_get_offset(&insert);
1956 
1957 #if GTK_CHECK_VERSION(2, 20, 0)
1958   // Nothing should be selected when we are in the middle of composition.
1959   DCHECK(preedit_.empty() || start_offset == end_offset);
1960   if (!preedit_.empty()) {
1961     start_offset += preedit_.size();
1962     end_offset += preedit_.size();
1963   }
1964 #endif
1965 
1966   return CharRange(start_offset, end_offset);
1967 }
1968 
ItersFromCharRange(const CharRange & range,GtkTextIter * iter_min,GtkTextIter * iter_max)1969 void AutocompleteEditViewGtk::ItersFromCharRange(const CharRange& range,
1970                                                  GtkTextIter* iter_min,
1971                                                  GtkTextIter* iter_max) {
1972   DCHECK(!IsImeComposing());
1973   gtk_text_buffer_get_iter_at_offset(text_buffer_, iter_min, range.cp_min);
1974   gtk_text_buffer_get_iter_at_offset(text_buffer_, iter_max, range.cp_max);
1975 }
1976 
GetTextLength() const1977 int AutocompleteEditViewGtk::GetTextLength() const {
1978   GtkTextIter end;
1979   gtk_text_buffer_get_iter_at_mark(text_buffer_, &end, instant_mark_);
1980 #if GTK_CHECK_VERSION(2, 20, 0)
1981   // We need to count the length of the text being composed, because we treat
1982   // it as part of the content in GetText().
1983   return gtk_text_iter_get_offset(&end) + preedit_.size();
1984 #else
1985   return gtk_text_iter_get_offset(&end);
1986 #endif
1987 }
1988 
PlaceCaretAt(int pos)1989 void AutocompleteEditViewGtk::PlaceCaretAt(int pos) {
1990   GtkTextIter cursor;
1991   gtk_text_buffer_get_iter_at_offset(text_buffer_, &cursor, pos);
1992   gtk_text_buffer_place_cursor(text_buffer_, &cursor);
1993 }
1994 
IsCaretAtEnd() const1995 bool AutocompleteEditViewGtk::IsCaretAtEnd() const {
1996   const CharRange selection = GetSelection();
1997   return selection.cp_min == selection.cp_max &&
1998       selection.cp_min == GetTextLength();
1999 }
2000 
EmphasizeURLComponents()2001 void AutocompleteEditViewGtk::EmphasizeURLComponents() {
2002 #if GTK_CHECK_VERSION(2, 20, 0)
2003   // We can't change the text style easily, if the preedit string (the text
2004   // being composed by the input method) is not empty, which is not treated as
2005   // a part of the text content inside GtkTextView. And it's ok to simply return
2006   // in this case, as this method will be called again when the preedit string
2007   // gets committed.
2008   if (preedit_.size()) {
2009     strikethrough_ = CharRange();
2010     return;
2011   }
2012 #endif
2013   // See whether the contents are a URL with a non-empty host portion, which we
2014   // should emphasize.  To check for a URL, rather than using the type returned
2015   // by Parse(), ask the model, which will check the desired page transition for
2016   // this input.  This can tell us whether an UNKNOWN input string is going to
2017   // be treated as a search or a navigation, and is the same method the Paste
2018   // And Go system uses.
2019   url_parse::Component scheme, host;
2020   string16 text(GetText());
2021   AutocompleteInput::ParseForEmphasizeComponents(
2022       text, model_->GetDesiredTLD(), &scheme, &host);
2023   const bool emphasize = model_->CurrentTextIsURL() && (host.len > 0);
2024 
2025   // Set the baseline emphasis.
2026   GtkTextIter start, end;
2027   GetTextBufferBounds(&start, &end);
2028   gtk_text_buffer_remove_all_tags(text_buffer_, &start, &end);
2029   if (emphasize) {
2030     gtk_text_buffer_apply_tag(text_buffer_, faded_text_tag_, &start, &end);
2031 
2032     // We've found a host name, give it more emphasis.
2033     gtk_text_buffer_get_iter_at_line_index(text_buffer_, &start, 0,
2034                                            GetUTF8Offset(text,
2035                                                          host.begin));
2036     gtk_text_buffer_get_iter_at_line_index(text_buffer_, &end, 0,
2037                                            GetUTF8Offset(text,
2038                                                          host.end()));
2039 
2040     gtk_text_buffer_apply_tag(text_buffer_, normal_text_tag_, &start, &end);
2041   } else {
2042     gtk_text_buffer_apply_tag(text_buffer_, normal_text_tag_, &start, &end);
2043   }
2044 
2045   strikethrough_ = CharRange();
2046   // Emphasize the scheme for security UI display purposes (if necessary).
2047   if (!model_->user_input_in_progress() && scheme.is_nonempty() &&
2048       (security_level_ != ToolbarModel::NONE)) {
2049     CharRange scheme_range = CharRange(GetUTF8Offset(text, scheme.begin),
2050                                        GetUTF8Offset(text, scheme.end()));
2051     ItersFromCharRange(scheme_range, &start, &end);
2052 
2053     if (security_level_ == ToolbarModel::SECURITY_ERROR) {
2054       strikethrough_ = scheme_range;
2055       // When we draw the strikethrough, we don't want to include the ':' at the
2056       // end of the scheme.
2057       strikethrough_.cp_max--;
2058 
2059       gtk_text_buffer_apply_tag(text_buffer_, security_error_scheme_tag_,
2060                                 &start, &end);
2061     } else if (security_level_ == ToolbarModel::SECURITY_WARNING) {
2062       gtk_text_buffer_apply_tag(text_buffer_, faded_text_tag_, &start, &end);
2063     } else {
2064       gtk_text_buffer_apply_tag(text_buffer_, secure_scheme_tag_, &start, &end);
2065     }
2066   }
2067 }
2068 
StopAnimation()2069 void AutocompleteEditViewGtk::StopAnimation() {
2070   // Clear the animation delegate so we don't get an AnimationEnded() callback.
2071   instant_animation_->set_delegate(NULL);
2072   instant_animation_->Stop();
2073   UpdateInstantViewColors();
2074 }
2075 
TextChanged()2076 void AutocompleteEditViewGtk::TextChanged() {
2077   EmphasizeURLComponents();
2078   model_->OnChanged();
2079 }
2080 
SavePrimarySelection(const std::string & selected_text)2081 void AutocompleteEditViewGtk::SavePrimarySelection(
2082     const std::string& selected_text) {
2083   DCHECK(text_view_);
2084 
2085   GtkClipboard* clipboard =
2086       gtk_widget_get_clipboard(text_view_, GDK_SELECTION_PRIMARY);
2087   DCHECK(clipboard);
2088   if (!clipboard)
2089     return;
2090 
2091   gtk_clipboard_set_text(
2092       clipboard, selected_text.data(), selected_text.size());
2093 }
2094 
SetTextAndSelectedRange(const string16 & text,const CharRange & range)2095 void AutocompleteEditViewGtk::SetTextAndSelectedRange(const string16& text,
2096                                                       const CharRange& range) {
2097   if (text != GetText()) {
2098     std::string utf8 = UTF16ToUTF8(text);
2099     gtk_text_buffer_set_text(text_buffer_, utf8.data(), utf8.length());
2100   }
2101   SetSelectedRange(range);
2102   AdjustTextJustification();
2103 }
2104 
SetSelectedRange(const CharRange & range)2105 void AutocompleteEditViewGtk::SetSelectedRange(const CharRange& range) {
2106   GtkTextIter insert, bound;
2107   ItersFromCharRange(range, &bound, &insert);
2108   gtk_text_buffer_select_range(text_buffer_, &insert, &bound);
2109 
2110   // This should be set *after* setting the selection range, in case setting the
2111   // selection triggers HandleMarkSet which sets |selection_suggested_| to
2112   // false.
2113   selection_suggested_ = true;
2114 }
2115 
AdjustTextJustification()2116 void AutocompleteEditViewGtk::AdjustTextJustification() {
2117   DCHECK(text_view_);
2118 
2119   PangoDirection content_dir = GetContentDirection();
2120 
2121   // Use keymap direction if content does not have strong direction.
2122   // It matches the behavior of GtkTextView.
2123   if (content_dir == PANGO_DIRECTION_NEUTRAL) {
2124     content_dir = gdk_keymap_get_direction(
2125       gdk_keymap_get_for_display(gtk_widget_get_display(text_view_)));
2126   }
2127 
2128   GtkTextDirection widget_dir = gtk_widget_get_direction(text_view_);
2129 
2130   if ((widget_dir == GTK_TEXT_DIR_RTL && content_dir == PANGO_DIRECTION_LTR) ||
2131       (widget_dir == GTK_TEXT_DIR_LTR && content_dir == PANGO_DIRECTION_RTL)) {
2132     gtk_text_view_set_justification(GTK_TEXT_VIEW(text_view_),
2133                                     GTK_JUSTIFY_RIGHT);
2134   } else {
2135     gtk_text_view_set_justification(GTK_TEXT_VIEW(text_view_),
2136                                     GTK_JUSTIFY_LEFT);
2137   }
2138 }
2139 
GetContentDirection()2140 PangoDirection AutocompleteEditViewGtk::GetContentDirection() {
2141   GtkTextIter iter;
2142   gtk_text_buffer_get_start_iter(text_buffer_, &iter);
2143 
2144   PangoDirection dir = PANGO_DIRECTION_NEUTRAL;
2145   do {
2146     dir = pango_unichar_direction(gtk_text_iter_get_char(&iter));
2147     if (dir != PANGO_DIRECTION_NEUTRAL)
2148       break;
2149   } while (gtk_text_iter_forward_char(&iter));
2150 
2151   return dir;
2152 }
2153 
HandleWidgetDirectionChanged(GtkWidget * sender,GtkTextDirection previous_direction)2154 void AutocompleteEditViewGtk::HandleWidgetDirectionChanged(
2155     GtkWidget* sender, GtkTextDirection previous_direction) {
2156   AdjustTextJustification();
2157 }
2158 
HandleDeleteFromCursor(GtkWidget * sender,GtkDeleteType type,gint count)2159 void AutocompleteEditViewGtk::HandleDeleteFromCursor(GtkWidget *sender,
2160     GtkDeleteType type, gint count) {
2161   // If the selected text was suggested for autocompletion, then erase those
2162   // first and then let the default handler take over.
2163   if (selection_suggested_) {
2164     gtk_text_buffer_delete_selection(text_buffer_, true, true);
2165     selection_suggested_ = false;
2166   }
2167 }
2168 
HandleKeymapDirectionChanged(GdkKeymap * sender)2169 void AutocompleteEditViewGtk::HandleKeymapDirectionChanged(GdkKeymap* sender) {
2170   AdjustTextJustification();
2171 }
2172 
HandleDeleteRange(GtkTextBuffer * buffer,GtkTextIter * start,GtkTextIter * end)2173 void AutocompleteEditViewGtk::HandleDeleteRange(GtkTextBuffer* buffer,
2174                                                 GtkTextIter* start,
2175                                                 GtkTextIter* end) {
2176   // Prevent the user from deleting the instant anchor. We can't simply set the
2177   // instant anchor readonly by applying a tag with "editable" = FALSE, because
2178   // it'll prevent the insert caret from blinking.
2179   ValidateTextBufferIter(start);
2180   ValidateTextBufferIter(end);
2181   if (!gtk_text_iter_compare(start, end)) {
2182     static guint signal_id =
2183         g_signal_lookup("delete-range", GTK_TYPE_TEXT_BUFFER);
2184     g_signal_stop_emission(buffer, signal_id, 0);
2185   }
2186 }
2187 
HandleMarkSetAlways(GtkTextBuffer * buffer,GtkTextIter * location,GtkTextMark * mark)2188 void AutocompleteEditViewGtk::HandleMarkSetAlways(GtkTextBuffer* buffer,
2189                                                   GtkTextIter* location,
2190                                                   GtkTextMark* mark) {
2191   if (mark == instant_mark_ || !instant_mark_)
2192     return;
2193 
2194   GtkTextIter new_iter = *location;
2195   ValidateTextBufferIter(&new_iter);
2196 
2197   static guint signal_id = g_signal_lookup("mark-set", GTK_TYPE_TEXT_BUFFER);
2198 
2199   // "mark-set" signal is actually emitted after the mark's location is already
2200   // set, so if the location is beyond the instant anchor, we need to move the
2201   // mark again, which will emit the signal again. In order to prevent other
2202   // signal handlers from being called twice, we need to stop signal emission
2203   // before moving the mark again.
2204   if (gtk_text_iter_compare(&new_iter, location)) {
2205     g_signal_stop_emission(buffer, signal_id, 0);
2206     gtk_text_buffer_move_mark(buffer, mark, &new_iter);
2207     return;
2208   }
2209 
2210   if (mark != gtk_text_buffer_get_insert(text_buffer_) &&
2211       mark != gtk_text_buffer_get_selection_bound(text_buffer_)) {
2212     return;
2213   }
2214 
2215   // See issue http://crbug.com/63860
2216   GtkTextIter insert;
2217   GtkTextIter selection_bound;
2218   gtk_text_buffer_get_iter_at_mark(buffer, &insert,
2219                                    gtk_text_buffer_get_insert(buffer));
2220   gtk_text_buffer_get_iter_at_mark(buffer, &selection_bound,
2221                                    gtk_text_buffer_get_selection_bound(buffer));
2222 
2223   GtkTextIter end;
2224   gtk_text_buffer_get_iter_at_mark(text_buffer_, &end, instant_mark_);
2225 
2226   if (gtk_text_iter_compare(&insert, &end) > 0 ||
2227       gtk_text_iter_compare(&selection_bound, &end) > 0) {
2228     g_signal_stop_emission(buffer, signal_id, 0);
2229   }
2230 }
2231 
2232 // static
ClipboardGetSelectionThunk(GtkClipboard * clipboard,GtkSelectionData * selection_data,guint info,gpointer object)2233 void AutocompleteEditViewGtk::ClipboardGetSelectionThunk(
2234     GtkClipboard* clipboard,
2235     GtkSelectionData* selection_data,
2236     guint info,
2237     gpointer object) {
2238   AutocompleteEditViewGtk* edit_view =
2239       reinterpret_cast<AutocompleteEditViewGtk*>(
2240           g_object_get_data(G_OBJECT(object), kAutocompleteEditViewGtkKey));
2241   edit_view->ClipboardGetSelection(clipboard, selection_data, info);
2242 }
2243 
ClipboardGetSelection(GtkClipboard * clipboard,GtkSelectionData * selection_data,guint info)2244 void AutocompleteEditViewGtk::ClipboardGetSelection(
2245     GtkClipboard* clipboard,
2246     GtkSelectionData* selection_data,
2247     guint info) {
2248   gtk_selection_data_set_text(selection_data, primary_selection_text_.c_str(),
2249                               primary_selection_text_.size());
2250 }
2251 
GetSelectedText() const2252 std::string AutocompleteEditViewGtk::GetSelectedText() const {
2253   GtkTextIter start, end;
2254   std::string result;
2255   if (gtk_text_buffer_get_selection_bounds(text_buffer_, &start, &end)) {
2256     gchar* text = gtk_text_iter_get_text(&start, &end);
2257     size_t text_len = strlen(text);
2258     if (text_len)
2259       result = std::string(text, text_len);
2260     g_free(text);
2261   }
2262   return result;
2263 }
2264 
UpdatePrimarySelectionIfValidURL()2265 void AutocompleteEditViewGtk::UpdatePrimarySelectionIfValidURL() {
2266   string16 text = UTF8ToUTF16(GetSelectedText());
2267 
2268   if (text.empty())
2269     return;
2270 
2271   // Use AdjustTextForCopy to make sure we prefix the text with 'http://'.
2272   CharRange selection = GetSelection();
2273   GURL url;
2274   bool write_url;
2275   model_->AdjustTextForCopy(selection.selection_min(), IsSelectAll(), &text,
2276                             &url, &write_url);
2277   if (write_url) {
2278     selected_text_ = UTF16ToUTF8(text);
2279     OwnPrimarySelection(selected_text_);
2280   }
2281 }
2282 
2283 #if GTK_CHECK_VERSION(2, 20, 0)
HandlePreeditChanged(GtkWidget * sender,const gchar * preedit)2284 void AutocompleteEditViewGtk::HandlePreeditChanged(GtkWidget* sender,
2285                                                    const gchar* preedit) {
2286   // GtkTextView won't fire "begin-user-action" and "end-user-action" signals
2287   // when changing the preedit string, so we need to call
2288   // OnBeforePossibleChange() and OnAfterPossibleChange() by ourselves.
2289   OnBeforePossibleChange();
2290   if (preedit && *preedit) {
2291     // GtkTextView will only delete the selection range when committing the
2292     // preedit string, which will cause very strange behavior, so we need to
2293     // delete the selection range here explicitly. See http://crbug.com/18808.
2294     if (preedit_.empty())
2295       gtk_text_buffer_delete_selection(text_buffer_, false, true);
2296     preedit_ = UTF8ToUTF16(preedit);
2297   } else {
2298     preedit_.clear();
2299   }
2300   OnAfterPossibleChange();
2301 }
2302 #endif
2303 
HandleWindowSetFocus(GtkWindow * sender,GtkWidget * focus)2304 void AutocompleteEditViewGtk::HandleWindowSetFocus(
2305     GtkWindow* sender, GtkWidget* focus) {
2306   // This is actually a guess. If the focused widget changes in "focus-out"
2307   // event handler, then the window will respect that and won't focus
2308   // |focus|. I doubt that is likely to happen however.
2309   going_to_focus_ = focus;
2310 }
2311 
HandleUndoRedo(GtkWidget * sender)2312 void AutocompleteEditViewGtk::HandleUndoRedo(GtkWidget* sender) {
2313   OnBeforePossibleChange();
2314 }
2315 
HandleUndoRedoAfter(GtkWidget * sender)2316 void AutocompleteEditViewGtk::HandleUndoRedoAfter(GtkWidget* sender) {
2317   OnAfterPossibleChange();
2318 }
2319 
GetTextBufferBounds(GtkTextIter * start,GtkTextIter * end) const2320 void AutocompleteEditViewGtk::GetTextBufferBounds(GtkTextIter* start,
2321                                                   GtkTextIter* end) const {
2322   gtk_text_buffer_get_start_iter(text_buffer_, start);
2323   gtk_text_buffer_get_iter_at_mark(text_buffer_, end, instant_mark_);
2324 }
2325 
ValidateTextBufferIter(GtkTextIter * iter) const2326 void AutocompleteEditViewGtk::ValidateTextBufferIter(GtkTextIter* iter) const {
2327   if (!instant_mark_)
2328     return;
2329 
2330   GtkTextIter end;
2331   gtk_text_buffer_get_iter_at_mark(text_buffer_, &end, instant_mark_);
2332   if (gtk_text_iter_compare(iter, &end) > 0)
2333     *iter = end;
2334 }
2335 
AdjustVerticalAlignmentOfInstantView()2336 void AutocompleteEditViewGtk::AdjustVerticalAlignmentOfInstantView() {
2337   // By default, GtkTextView layouts an anchored child widget just above the
2338   // baseline, so we need to move the |instant_view_| down to make sure it
2339   // has the same baseline as the |text_view_|.
2340   PangoLayout* layout = gtk_label_get_layout(GTK_LABEL(instant_view_));
2341   int height;
2342   pango_layout_get_size(layout, NULL, &height);
2343   PangoLayoutIter* iter = pango_layout_get_iter(layout);
2344   int baseline = pango_layout_iter_get_baseline(iter);
2345   pango_layout_iter_free(iter);
2346   g_object_set(instant_anchor_tag_, "rise", baseline - height, NULL);
2347 }
2348