Disable TabDragController tests that fail with a real compositor.
[chromium-blink-merge.git] / chrome / browser / ui / gtk / omnibox / omnibox_view_gtk.cc
blob53bc239520b52c2e422bd5db173132613d2be24e
1 // Copyright 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "chrome/browser/ui/gtk/omnibox/omnibox_view_gtk.h"
7 #include <gdk/gdkkeysyms.h>
8 #include <gtk/gtk.h>
10 #include <algorithm>
12 #include "base/logging.h"
13 #include "base/metrics/histogram.h"
14 #include "base/strings/string_util.h"
15 #include "base/strings/utf_string_conversion_utils.h"
16 #include "base/strings/utf_string_conversions.h"
17 #include "chrome/app/chrome_command_ids.h"
18 #include "chrome/browser/autocomplete/autocomplete_input.h"
19 #include "chrome/browser/autocomplete/autocomplete_match.h"
20 #include "chrome/browser/bookmarks/bookmark_node_data.h"
21 #include "chrome/browser/chrome_notification_types.h"
22 #include "chrome/browser/command_updater.h"
23 #include "chrome/browser/defaults.h"
24 #include "chrome/browser/platform_util.h"
25 #include "chrome/browser/search/search.h"
26 #include "chrome/browser/ui/browser.h"
27 #include "chrome/browser/ui/gtk/gtk_theme_service.h"
28 #include "chrome/browser/ui/gtk/gtk_util.h"
29 #include "chrome/browser/ui/gtk/location_bar_view_gtk.h"
30 #include "chrome/browser/ui/gtk/omnibox/omnibox_popup_view_gtk.h"
31 #include "chrome/browser/ui/gtk/view_id_util.h"
32 #include "chrome/browser/ui/omnibox/omnibox_edit_controller.h"
33 #include "chrome/browser/ui/omnibox/omnibox_edit_model.h"
34 #include "chrome/browser/ui/omnibox/omnibox_popup_model.h"
35 #include "chrome/browser/ui/tabs/tab_strip_model.h"
36 #include "chrome/browser/ui/toolbar/toolbar_model.h"
37 #include "content/public/browser/notification_source.h"
38 #include "content/public/browser/web_contents.h"
39 #include "extensions/common/constants.h"
40 #include "grit/generated_resources.h"
41 #include "net/base/escape.h"
42 #include "third_party/undoview/undo_view.h"
43 #include "ui/base/accelerators/menu_label_accelerator_util_linux.h"
44 #include "ui/base/dragdrop/drag_drop_types.h"
45 #include "ui/base/dragdrop/gtk_dnd_util.h"
46 #include "ui/base/gtk/gtk_hig_constants.h"
47 #include "ui/base/l10n/l10n_util.h"
48 #include "ui/base/resource/resource_bundle.h"
49 #include "ui/gfx/color_utils.h"
50 #include "ui/gfx/font.h"
51 #include "ui/gfx/gtk_compat.h"
52 #include "ui/gfx/skia_utils_gtk.h"
53 #include "url/gurl.h"
55 using content::WebContents;
57 namespace {
59 const gchar* kOmniboxViewGtkKey = "__OMNIBOX_VIEW_GTK__";
61 const char kTextBaseColor[] = "#808080";
62 const char kSecureSchemeColor[] = "#079500";
63 const char kSecurityErrorSchemeColor[] = "#a20000";
65 const double kStrikethroughStrokeRed = 162.0 / 256.0;
66 const double kStrikethroughStrokeWidth = 2.0;
68 size_t GetUTF8Offset(const base::string16& text, size_t text_offset) {
69 return base::UTF16ToUTF8(text.substr(0, text_offset)).size();
72 // Stores GTK+-specific state so it can be restored after switching tabs.
73 struct ViewState {
74 explicit ViewState(const OmniboxViewGtk::CharRange& selection_range)
75 : selection_range(selection_range) {
78 // Range of selected text.
79 OmniboxViewGtk::CharRange selection_range;
82 const char kAutocompleteEditStateKey[] = "AutocompleteEditState";
84 struct AutocompleteEditState : public base::SupportsUserData::Data {
85 AutocompleteEditState(const OmniboxEditModel::State& model_state,
86 const ViewState& view_state)
87 : model_state(model_state),
88 view_state(view_state) {
90 virtual ~AutocompleteEditState() {}
92 const OmniboxEditModel::State model_state;
93 const ViewState view_state;
96 // Set up style properties to override the default GtkTextView; if a theme has
97 // overridden some of these properties, an inner-line will be displayed inside
98 // the fake GtkTextEntry.
99 void SetEntryStyle() {
100 static bool style_was_set = false;
102 if (style_was_set)
103 return;
104 style_was_set = true;
106 gtk_rc_parse_string(
107 "style \"chrome-location-bar-entry\" {"
108 " xthickness = 0\n"
109 " ythickness = 0\n"
110 " GtkWidget::focus_padding = 0\n"
111 " GtkWidget::focus-line-width = 0\n"
112 " GtkWidget::interior_focus = 0\n"
113 " GtkWidget::internal-padding = 0\n"
114 " GtkContainer::border-width = 0\n"
115 "}\n"
116 "widget \"*chrome-location-bar-entry\" "
117 "style \"chrome-location-bar-entry\"");
120 // Copied from GTK+. Called when we lose the primary selection. This will clear
121 // the selection in the text buffer.
122 void ClipboardSelectionCleared(GtkClipboard* clipboard,
123 gpointer data) {
124 GtkTextIter insert;
125 GtkTextIter selection_bound;
126 GtkTextBuffer* buffer = GTK_TEXT_BUFFER(data);
128 gtk_text_buffer_get_iter_at_mark(buffer, &insert,
129 gtk_text_buffer_get_insert(buffer));
130 gtk_text_buffer_get_iter_at_mark(buffer, &selection_bound,
131 gtk_text_buffer_get_selection_bound(buffer));
133 if (!gtk_text_iter_equal(&insert, &selection_bound)) {
134 gtk_text_buffer_move_mark(buffer,
135 gtk_text_buffer_get_selection_bound(buffer),
136 &insert);
140 // Returns the |menu| item whose label matches |label|.
141 guint GetPopupMenuIndexForStockLabel(const char* label, GtkMenu* menu) {
142 GList* list = gtk_container_get_children(GTK_CONTAINER(menu));
143 guint index = 1;
144 for (GList* item = list; item != NULL; item = item->next, ++index) {
145 if (GTK_IS_IMAGE_MENU_ITEM(item->data)) {
146 gboolean is_stock = gtk_image_menu_item_get_use_stock(
147 GTK_IMAGE_MENU_ITEM(item->data));
148 if (is_stock) {
149 std::string menu_item_label =
150 gtk_menu_item_get_label(GTK_MENU_ITEM(item->data));
151 if (menu_item_label == label)
152 break;
156 g_list_free(list);
157 return index;
160 } // namespace
162 OmniboxViewGtk::OmniboxViewGtk(OmniboxEditController* controller,
163 Browser* browser,
164 Profile* profile,
165 CommandUpdater* command_updater,
166 bool popup_window_mode,
167 GtkWidget* location_bar)
168 : OmniboxView(profile, controller, command_updater),
169 browser_(browser),
170 text_view_(NULL),
171 tag_table_(NULL),
172 text_buffer_(NULL),
173 faded_text_tag_(NULL),
174 secure_scheme_tag_(NULL),
175 security_error_scheme_tag_(NULL),
176 normal_text_tag_(NULL),
177 gray_text_anchor_tag_(NULL),
178 gray_text_view_(NULL),
179 gray_text_mark_(NULL),
180 popup_window_mode_(popup_window_mode),
181 security_level_(ToolbarModel::NONE),
182 mark_set_handler_id_(0),
183 button_1_pressed_(false),
184 theme_service_(GtkThemeService::GetFrom(profile)),
185 enter_was_pressed_(false),
186 tab_was_pressed_(false),
187 paste_clipboard_requested_(false),
188 enter_was_inserted_(false),
189 selection_suggested_(false),
190 delete_was_pressed_(false),
191 delete_at_end_pressed_(false),
192 handling_key_press_(false),
193 content_maybe_changed_by_key_press_(false),
194 update_popup_without_focus_(false),
195 supports_pre_edit_(!gtk_check_version(2, 20, 0)),
196 pre_edit_size_before_change_(0),
197 going_to_focus_(NULL) {
198 OmniboxPopupViewGtk* view = new OmniboxPopupViewGtk(
199 GetFont(), this, model(), location_bar);
200 view->Init();
201 popup_view_.reset(view);
204 OmniboxViewGtk::~OmniboxViewGtk() {
205 // Explicitly teardown members which have a reference to us. Just to be safe
206 // we want them to be destroyed before destroying any other internal state.
207 popup_view_.reset();
209 // We own our widget and TextView related objects.
210 if (alignment_.get()) { // Init() has been called.
211 alignment_.Destroy();
212 g_object_unref(text_buffer_);
213 g_object_unref(tag_table_);
214 // The tags we created are owned by the tag_table, and should be destroyed
215 // along with it. We don't hold our own reference to them.
219 void OmniboxViewGtk::Init() {
220 SetEntryStyle();
222 // The height of the text view is going to change based on the font used. We
223 // don't want to stretch the height, and we want it vertically centered.
224 alignment_.Own(gtk_alignment_new(0., 0.5, 1.0, 0.0));
225 gtk_widget_set_name(alignment_.get(),
226 "chrome-autocomplete-edit-view");
228 // The GtkTagTable and GtkTextBuffer are not initially unowned, so we have
229 // our own reference when we create them, and we own them. Adding them to
230 // the other objects adds a reference; it doesn't adopt them.
231 tag_table_ = gtk_text_tag_table_new();
232 text_buffer_ = gtk_text_buffer_new(tag_table_);
233 g_object_set_data(G_OBJECT(text_buffer_), kOmniboxViewGtkKey, this);
235 // We need to run this two handlers before undo manager's handlers, so that
236 // text iterators modified by these handlers can be passed down to undo
237 // manager's handlers.
238 g_signal_connect(text_buffer_, "delete-range",
239 G_CALLBACK(&HandleDeleteRangeThunk), this);
240 g_signal_connect(text_buffer_, "mark-set",
241 G_CALLBACK(&HandleMarkSetAlwaysThunk), this);
243 text_view_ = gtk_undo_view_new(text_buffer_);
244 if (popup_window_mode_)
245 gtk_text_view_set_editable(GTK_TEXT_VIEW(text_view_), false);
247 // One pixel left margin is necessary to make the cursor visible when UI
248 // language direction is LTR but |text_buffer_|'s content direction is RTL.
249 gtk_text_view_set_left_margin(GTK_TEXT_VIEW(text_view_), 1);
251 // See SetEntryStyle() comments.
252 gtk_widget_set_name(text_view_, "chrome-location-bar-entry");
254 // The text view was floating. It will now be owned by the alignment.
255 gtk_container_add(GTK_CONTAINER(alignment_.get()), text_view_);
257 // Do not allow inserting tab characters when pressing Tab key, so that when
258 // Tab key is pressed, |text_view_| will emit "move-focus" signal, which will
259 // be intercepted by our own handler to trigger Tab to search feature when
260 // necessary.
261 gtk_text_view_set_accepts_tab(GTK_TEXT_VIEW(text_view_), FALSE);
263 faded_text_tag_ = gtk_text_buffer_create_tag(text_buffer_,
264 NULL, "foreground", kTextBaseColor, NULL);
265 secure_scheme_tag_ = gtk_text_buffer_create_tag(text_buffer_,
266 NULL, "foreground", kSecureSchemeColor, NULL);
267 security_error_scheme_tag_ = gtk_text_buffer_create_tag(text_buffer_,
268 NULL, "foreground", kSecurityErrorSchemeColor, NULL);
269 normal_text_tag_ = gtk_text_buffer_create_tag(text_buffer_,
270 NULL, "foreground", "#000000", NULL);
272 // NOTE: This code used to connect to "changed", however this was fired too
273 // often and during bad times (our own buffer changes?). It works out much
274 // better to listen to end-user-action, which should be fired whenever the
275 // user makes some sort of change to the buffer.
276 g_signal_connect(text_buffer_, "begin-user-action",
277 G_CALLBACK(&HandleBeginUserActionThunk), this);
278 g_signal_connect(text_buffer_, "end-user-action",
279 G_CALLBACK(&HandleEndUserActionThunk), this);
280 // We connect to key press and release for special handling of a few keys.
281 g_signal_connect(text_view_, "key-press-event",
282 G_CALLBACK(&HandleKeyPressThunk), this);
283 g_signal_connect(text_view_, "key-release-event",
284 G_CALLBACK(&HandleKeyReleaseThunk), this);
285 g_signal_connect(text_view_, "button-press-event",
286 G_CALLBACK(&HandleViewButtonPressThunk), this);
287 g_signal_connect(text_view_, "button-release-event",
288 G_CALLBACK(&HandleViewButtonReleaseThunk), this);
289 g_signal_connect(text_view_, "focus-in-event",
290 G_CALLBACK(&HandleViewFocusInThunk), this);
291 g_signal_connect(text_view_, "focus-out-event",
292 G_CALLBACK(&HandleViewFocusOutThunk), this);
293 // NOTE: The GtkTextView documentation asks you not to connect to this
294 // signal, but it is very convenient and clean for catching up/down.
295 g_signal_connect(text_view_, "move-cursor",
296 G_CALLBACK(&HandleViewMoveCursorThunk), this);
297 g_signal_connect(text_view_, "move-focus",
298 G_CALLBACK(&HandleViewMoveFocusThunk), this);
299 // Override the size request. We want to keep the original height request
300 // from the widget, since that's font dependent. We want to ignore the width
301 // so we don't force a minimum width based on the text length.
302 g_signal_connect(text_view_, "size-request",
303 G_CALLBACK(&HandleViewSizeRequestThunk), this);
304 g_signal_connect(text_view_, "populate-popup",
305 G_CALLBACK(&HandlePopulatePopupThunk), this);
306 mark_set_handler_id_ = g_signal_connect(
307 text_buffer_, "mark-set", G_CALLBACK(&HandleMarkSetThunk), this);
308 mark_set_handler_id2_ = g_signal_connect_after(
309 text_buffer_, "mark-set", G_CALLBACK(&HandleMarkSetAfterThunk), this);
310 g_signal_connect(text_view_, "drag-data-received",
311 G_CALLBACK(&HandleDragDataReceivedThunk), this);
312 // Override the text_view_'s default drag-data-get handler by calling our own
313 // version after the normal call has happened.
314 g_signal_connect_after(text_view_, "drag-data-get",
315 G_CALLBACK(&HandleDragDataGetThunk), this);
316 g_signal_connect_after(text_view_, "drag-begin",
317 G_CALLBACK(&HandleDragBeginThunk), this);
318 g_signal_connect_after(text_view_, "drag-end",
319 G_CALLBACK(&HandleDragEndThunk), this);
320 g_signal_connect(text_view_, "backspace",
321 G_CALLBACK(&HandleBackSpaceThunk), this);
322 g_signal_connect(text_view_, "copy-clipboard",
323 G_CALLBACK(&HandleCopyClipboardThunk), this);
324 g_signal_connect(text_view_, "cut-clipboard",
325 G_CALLBACK(&HandleCutClipboardThunk), this);
326 g_signal_connect(text_view_, "paste-clipboard",
327 G_CALLBACK(&HandlePasteClipboardThunk), this);
328 g_signal_connect_after(text_view_, "expose-event",
329 G_CALLBACK(&HandleExposeEventThunk), this);
330 g_signal_connect(text_view_, "direction-changed",
331 G_CALLBACK(&HandleWidgetDirectionChangedThunk), this);
332 g_signal_connect(text_view_, "delete-from-cursor",
333 G_CALLBACK(&HandleDeleteFromCursorThunk), this);
334 g_signal_connect(text_view_, "hierarchy-changed",
335 G_CALLBACK(&HandleHierarchyChangedThunk), this);
336 if (supports_pre_edit_) {
337 g_signal_connect(text_view_, "preedit-changed",
338 G_CALLBACK(&HandlePreEditChangedThunk), this);
340 g_signal_connect(text_view_, "undo", G_CALLBACK(&HandleUndoRedoThunk), this);
341 g_signal_connect(text_view_, "redo", G_CALLBACK(&HandleUndoRedoThunk), this);
342 g_signal_connect_after(text_view_, "undo",
343 G_CALLBACK(&HandleUndoRedoAfterThunk), this);
344 g_signal_connect_after(text_view_, "redo",
345 G_CALLBACK(&HandleUndoRedoAfterThunk), this);
346 g_signal_connect(text_view_, "destroy",
347 G_CALLBACK(&gtk_widget_destroyed), &text_view_);
349 // Setup for the gray suggestion text view.
350 // GtkLabel is used instead of GtkTextView to get transparent background.
351 gray_text_view_ = gtk_label_new(NULL);
352 gtk_widget_set_no_show_all(gray_text_view_, TRUE);
353 gtk_label_set_selectable(GTK_LABEL(gray_text_view_), TRUE);
355 GtkTextIter end_iter;
356 gtk_text_buffer_get_end_iter(text_buffer_, &end_iter);
358 // Insert a Zero Width Space character just before the gray text anchor.
359 // It's a hack to workaround a bug of GtkTextView which can not align the
360 // pre-edit string and a child anchor correctly when there is no other content
361 // around the pre-edit string.
362 gtk_text_buffer_insert(text_buffer_, &end_iter, "\342\200\213", -1);
363 GtkTextChildAnchor* gray_text_anchor =
364 gtk_text_buffer_create_child_anchor(text_buffer_, &end_iter);
366 gtk_text_view_add_child_at_anchor(GTK_TEXT_VIEW(text_view_),
367 gray_text_view_,
368 gray_text_anchor);
370 gray_text_anchor_tag_ = gtk_text_buffer_create_tag(text_buffer_, NULL, NULL);
372 GtkTextIter anchor_iter;
373 gtk_text_buffer_get_iter_at_child_anchor(text_buffer_, &anchor_iter,
374 gray_text_anchor);
375 gtk_text_buffer_apply_tag(text_buffer_, gray_text_anchor_tag_,
376 &anchor_iter, &end_iter);
378 GtkTextIter start_iter;
379 gtk_text_buffer_get_start_iter(text_buffer_, &start_iter);
380 gray_text_mark_ =
381 gtk_text_buffer_create_mark(text_buffer_, NULL, &start_iter, FALSE);
383 // Hooking up this handler after setting up above hacks for gray text view, so
384 // that we won't filter out the special ZWP mark itself.
385 g_signal_connect(text_buffer_, "insert-text",
386 G_CALLBACK(&HandleInsertTextThunk), this);
388 AdjustVerticalAlignmentOfGrayTextView();
390 registrar_.Add(this,
391 chrome::NOTIFICATION_BROWSER_THEME_CHANGED,
392 content::Source<ThemeService>(theme_service_));
393 theme_service_->InitThemesFor(this);
395 ViewIDUtil::SetID(GetNativeView(), VIEW_ID_OMNIBOX);
398 void OmniboxViewGtk::HandleHierarchyChanged(GtkWidget* sender,
399 GtkWidget* old_toplevel) {
400 GtkWindow* new_toplevel = platform_util::GetTopLevel(sender);
401 if (!new_toplevel)
402 return;
404 // Use |signals_| to make sure we don't get called back after destruction.
405 signals_.Connect(new_toplevel, "set-focus",
406 G_CALLBACK(&HandleWindowSetFocusThunk), this);
409 void OmniboxViewGtk::SetFocus() {
410 DCHECK(text_view_);
411 gtk_widget_grab_focus(text_view_);
412 // Restore caret visibility if focus is explicitly requested. This is
413 // necessary because if we already have invisible focus, the RequestFocus()
414 // call above will short-circuit, preventing us from reaching
415 // OmniboxEditModel::OnSetFocus(), which handles restoring visibility when the
416 // omnibox regains focus after losing focus.
417 model()->SetCaretVisibility(true);
420 void OmniboxViewGtk::ApplyCaretVisibility() {
421 gtk_text_view_set_cursor_visible(GTK_TEXT_VIEW(text_view_),
422 model()->is_caret_visible());
425 void OmniboxViewGtk::SaveStateToTab(WebContents* tab) {
426 DCHECK(tab);
427 // If any text has been selected, register it as the PRIMARY selection so it
428 // can still be pasted via middle-click after the text view is cleared.
429 if (!selected_text_.empty())
430 SavePrimarySelection(selected_text_);
431 // NOTE: GetStateForTabSwitch may affect GetSelection, so order is important.
432 OmniboxEditModel::State model_state = model()->GetStateForTabSwitch();
433 tab->SetUserData(
434 kAutocompleteEditStateKey,
435 new AutocompleteEditState(model_state, ViewState(GetSelection())));
438 void OmniboxViewGtk::OnTabChanged(const WebContents* web_contents) {
439 security_level_ = controller()->GetToolbarModel()->GetSecurityLevel(false);
440 selected_text_.clear();
442 const AutocompleteEditState* state = static_cast<AutocompleteEditState*>(
443 web_contents->GetUserData(&kAutocompleteEditStateKey));
444 model()->RestoreState(state ? &state->model_state : NULL);
445 if (state) {
446 // Move the marks for the cursor and the other end of the selection to the
447 // previously-saved offsets (but preserve PRIMARY).
448 StartUpdatingHighlightedText();
449 SetSelectedRange(state->view_state.selection_range);
450 FinishUpdatingHighlightedText();
454 void OmniboxViewGtk::Update() {
455 const ToolbarModel::SecurityLevel old_security_level = security_level_;
456 security_level_ = controller()->GetToolbarModel()->GetSecurityLevel(false);
457 if (model()->UpdatePermanentText()) {
458 // Something visibly changed. Re-enable URL replacement.
459 controller()->GetToolbarModel()->set_url_replacement_enabled(true);
460 model()->UpdatePermanentText();
462 RevertAll();
463 } else if (old_security_level != security_level_) {
464 EmphasizeURLComponents();
468 base::string16 OmniboxViewGtk::GetText() const {
469 GtkTextIter start, end;
470 GetTextBufferBounds(&start, &end);
471 gchar* utf8 = gtk_text_buffer_get_text(text_buffer_, &start, &end, false);
472 base::string16 out(base::UTF8ToUTF16(utf8));
473 g_free(utf8);
475 if (supports_pre_edit_) {
476 // We need to treat the text currently being composed by the input method
477 // as part of the text content, so that omnibox can work correctly in the
478 // middle of composition.
479 if (pre_edit_.size()) {
480 GtkTextMark* mark = gtk_text_buffer_get_insert(text_buffer_);
481 gtk_text_buffer_get_iter_at_mark(text_buffer_, &start, mark);
482 out.insert(gtk_text_iter_get_offset(&start), pre_edit_);
485 return out;
488 void OmniboxViewGtk::SetWindowTextAndCaretPos(const base::string16& text,
489 size_t caret_pos,
490 bool update_popup,
491 bool notify_text_changed) {
492 CharRange range(static_cast<int>(caret_pos), static_cast<int>(caret_pos));
493 SetTextAndSelectedRange(text, range);
495 if (update_popup)
496 UpdatePopup();
498 if (notify_text_changed)
499 TextChanged();
502 void OmniboxViewGtk::SetForcedQuery() {
503 const base::string16 current_text(GetText());
504 const size_t start = current_text.find_first_not_of(base::kWhitespaceUTF16);
505 if (start == base::string16::npos || (current_text[start] != '?')) {
506 SetUserText(base::ASCIIToUTF16("?"));
507 } else {
508 StartUpdatingHighlightedText();
509 SetSelectedRange(CharRange(current_text.size(), start + 1));
510 FinishUpdatingHighlightedText();
514 bool OmniboxViewGtk::IsSelectAll() const {
515 GtkTextIter sel_start, sel_end;
516 gtk_text_buffer_get_selection_bounds(text_buffer_, &sel_start, &sel_end);
518 GtkTextIter start, end;
519 GetTextBufferBounds(&start, &end);
521 // Returns true if the |text_buffer_| is empty.
522 return gtk_text_iter_equal(&start, &sel_start) &&
523 gtk_text_iter_equal(&end, &sel_end);
526 bool OmniboxViewGtk::DeleteAtEndPressed() {
527 return delete_at_end_pressed_;
530 void OmniboxViewGtk::GetSelectionBounds(base::string16::size_type* start,
531 base::string16::size_type* end) const {
532 CharRange selection = GetSelection();
533 *start = static_cast<size_t>(selection.cp_min);
534 *end = static_cast<size_t>(selection.cp_max);
537 void OmniboxViewGtk::SelectAll(bool reversed) {
538 // SelectAll() is invoked as a side effect of other actions (e.g. switching
539 // tabs or hitting Escape) in autocomplete_edit.cc, so we don't update the
540 // PRIMARY selection here.
541 SelectAllInternal(reversed, false);
544 void OmniboxViewGtk::UpdatePopup() {
545 model()->SetInputInProgress(true);
546 if (!update_popup_without_focus_ && !model()->has_focus())
547 return;
549 // Don't inline autocomplete when the caret/selection isn't at the end of
550 // the text, or in the middle of composition.
551 CharRange sel = GetSelection();
552 bool no_inline_autocomplete =
553 std::max(sel.cp_max, sel.cp_min) < GetOmniboxTextLength() ||
554 IsImeComposing();
555 model()->StartAutocomplete(sel.cp_min != sel.cp_max, no_inline_autocomplete);
558 void OmniboxViewGtk::OnTemporaryTextMaybeChanged(
559 const base::string16& display_text,
560 bool save_original_selection,
561 bool notify_text_changed) {
562 if (save_original_selection)
563 saved_temporary_selection_ = GetSelection();
565 StartUpdatingHighlightedText();
566 SetWindowTextAndCaretPos(display_text, display_text.length(), false, false);
567 FinishUpdatingHighlightedText();
568 if (notify_text_changed)
569 TextChanged();
572 bool OmniboxViewGtk::OnInlineAutocompleteTextMaybeChanged(
573 const base::string16& display_text,
574 size_t user_text_length) {
575 if (display_text == GetText())
576 return false;
578 StartUpdatingHighlightedText();
579 CharRange range(display_text.size(), user_text_length);
580 SetTextAndSelectedRange(display_text, range);
581 FinishUpdatingHighlightedText();
582 TextChanged();
583 return true;
586 void OmniboxViewGtk::OnInlineAutocompleteTextCleared() {
589 void OmniboxViewGtk::OnRevertTemporaryText() {
590 StartUpdatingHighlightedText();
591 SetSelectedRange(saved_temporary_selection_);
592 FinishUpdatingHighlightedText();
593 // We got here because the user hit the Escape key. We explicitly don't call
594 // TextChanged(), since OmniboxPopupModel::ResetToDefaultMatch() has already
595 // been called by now, and it would've called TextChanged() if it was
596 // warranted.
599 void OmniboxViewGtk::OnBeforePossibleChange() {
600 // Record this paste, so we can do different behavior.
601 if (paste_clipboard_requested_) {
602 paste_clipboard_requested_ = false;
603 model()->OnPaste();
606 // This method will be called in HandleKeyPress() method just before
607 // handling a key press event. So we should prevent it from being called
608 // when handling the key press event.
609 if (handling_key_press_)
610 return;
612 // Record our state.
613 text_before_change_ = GetText();
614 sel_before_change_ = GetSelection();
615 if (supports_pre_edit_)
616 pre_edit_size_before_change_ = pre_edit_.size();
619 // TODO(deanm): This is mostly stolen from Windows, and will need some work.
620 bool OmniboxViewGtk::OnAfterPossibleChange() {
621 // This method will be called in HandleKeyPress() method just after
622 // handling a key press event. So we should prevent it from being called
623 // when handling the key press event.
624 if (handling_key_press_) {
625 content_maybe_changed_by_key_press_ = true;
626 return false;
629 // If the change is caused by an Enter key press event, and the event was not
630 // handled by IME, then it's an unexpected change and shall be reverted here.
631 // {Start|Finish}UpdatingHighlightedText() are called here to prevent the
632 // PRIMARY selection from being changed.
633 if (enter_was_pressed_ && enter_was_inserted_) {
634 StartUpdatingHighlightedText();
635 SetTextAndSelectedRange(text_before_change_, sel_before_change_);
636 FinishUpdatingHighlightedText();
637 return false;
640 const CharRange new_sel = GetSelection();
641 const int length = GetOmniboxTextLength();
642 const bool selection_differs =
643 ((new_sel.cp_min != new_sel.cp_max) ||
644 (sel_before_change_.cp_min != sel_before_change_.cp_max)) &&
645 ((new_sel.cp_min != sel_before_change_.cp_min) ||
646 (new_sel.cp_max != sel_before_change_.cp_max));
647 const bool at_end_of_edit =
648 (new_sel.cp_min == length && new_sel.cp_max == length);
650 // See if the text or selection have changed since OnBeforePossibleChange().
651 const base::string16 new_text(GetText());
652 text_changed_ = (new_text != text_before_change_) || (supports_pre_edit_ &&
653 (pre_edit_.size() != pre_edit_size_before_change_));
655 if (text_changed_)
656 AdjustTextJustification();
658 // When the user has deleted text, we don't allow inline autocomplete. Make
659 // sure to not flag cases like selecting part of the text and then pasting
660 // (or typing) the prefix of that selection. (We detect these by making
661 // sure the caret, which should be after any insertion, hasn't moved
662 // forward of the old selection start.)
663 const bool just_deleted_text =
664 (text_before_change_.length() > new_text.length()) &&
665 (new_sel.cp_min <= std::min(sel_before_change_.cp_min,
666 sel_before_change_.cp_max));
668 delete_at_end_pressed_ = false;
670 const bool something_changed = model()->OnAfterPossibleChange(
671 text_before_change_, new_text, new_sel.selection_min(),
672 new_sel.selection_max(), selection_differs, text_changed_,
673 just_deleted_text, !IsImeComposing());
675 // If only selection was changed, we don't need to call the controller's
676 // OnChanged() method, which is called in TextChanged().
677 // But we still need to call EmphasizeURLComponents() to make sure the text
678 // attributes are updated correctly.
679 if (something_changed && text_changed_) {
680 TextChanged();
681 } else if (selection_differs) {
682 EmphasizeURLComponents();
683 } else if (delete_was_pressed_ && at_end_of_edit) {
684 delete_at_end_pressed_ = true;
685 model()->OnChanged();
687 delete_was_pressed_ = false;
689 return something_changed;
692 gfx::NativeView OmniboxViewGtk::GetNativeView() const {
693 return alignment_.get();
696 gfx::NativeView OmniboxViewGtk::GetRelativeWindowForPopup() const {
697 GtkWidget* toplevel = gtk_widget_get_toplevel(GetNativeView());
698 DCHECK(gtk_widget_is_toplevel(toplevel));
699 return toplevel;
702 void OmniboxViewGtk::SetGrayTextAutocompletion(
703 const base::string16& suggestion) {
704 std::string suggestion_utf8 = base::UTF16ToUTF8(suggestion);
706 gtk_label_set_text(GTK_LABEL(gray_text_view_), suggestion_utf8.c_str());
708 if (suggestion.empty()) {
709 gtk_widget_hide(gray_text_view_);
710 return;
713 gtk_widget_show(gray_text_view_);
714 AdjustVerticalAlignmentOfGrayTextView();
715 UpdateGrayTextViewColors();
718 base::string16 OmniboxViewGtk::GetGrayTextAutocompletion() const {
719 const gchar* suggestion = gtk_label_get_text(GTK_LABEL(gray_text_view_));
720 return suggestion ? base::UTF8ToUTF16(suggestion) : base::string16();
723 int OmniboxViewGtk::GetTextWidth() const {
724 // TextWidth may be called after gtk widget tree is destroyed but
725 // before OmniboxViewGtk gets deleted. This is a safe guard
726 // to avoid accessing |text_view_| that has already been destroyed.
727 // See crbug.com/70192.
728 if (!text_view_)
729 return 0;
731 int horizontal_border_size =
732 gtk_text_view_get_border_window_size(GTK_TEXT_VIEW(text_view_),
733 GTK_TEXT_WINDOW_LEFT) +
734 gtk_text_view_get_border_window_size(GTK_TEXT_VIEW(text_view_),
735 GTK_TEXT_WINDOW_RIGHT) +
736 gtk_text_view_get_left_margin(GTK_TEXT_VIEW(text_view_)) +
737 gtk_text_view_get_right_margin(GTK_TEXT_VIEW(text_view_));
739 GtkTextIter start, end;
740 GdkRectangle first_char_bounds, last_char_bounds;
741 gtk_text_buffer_get_start_iter(text_buffer_, &start);
743 // Use the real end iterator here to take the width of gray suggestion text
744 // into account, so that location bar can layout its children correctly.
745 gtk_text_buffer_get_end_iter(text_buffer_, &end);
746 gtk_text_view_get_iter_location(GTK_TEXT_VIEW(text_view_),
747 &start, &first_char_bounds);
748 gtk_text_view_get_iter_location(GTK_TEXT_VIEW(text_view_),
749 &end, &last_char_bounds);
751 gint first_char_start = first_char_bounds.x;
752 gint first_char_end = first_char_start + first_char_bounds.width;
753 gint last_char_start = last_char_bounds.x;
754 gint last_char_end = last_char_start + last_char_bounds.width;
756 // bounds width could be negative for RTL text.
757 if (first_char_start > first_char_end)
758 std::swap(first_char_start, first_char_end);
759 if (last_char_start > last_char_end)
760 std::swap(last_char_start, last_char_end);
762 gint text_width = first_char_start < last_char_start ?
763 last_char_end - first_char_start : first_char_end - last_char_start;
765 return text_width + horizontal_border_size;
768 int OmniboxViewGtk::GetWidth() const {
769 GtkAllocation allocation;
770 gtk_widget_get_allocation(text_view_, &allocation);
771 return allocation.width;
774 bool OmniboxViewGtk::IsImeComposing() const {
775 return supports_pre_edit_ && !pre_edit_.empty();
778 void OmniboxViewGtk::Observe(int type,
779 const content::NotificationSource& source,
780 const content::NotificationDetails& details) {
781 DCHECK(type == chrome::NOTIFICATION_BROWSER_THEME_CHANGED);
783 SetBaseColor();
786 void OmniboxViewGtk::SetBaseColor() {
787 DCHECK(text_view_);
789 bool use_gtk = theme_service_->UsingNativeTheme();
790 if (use_gtk) {
791 gtk_widget_modify_cursor(text_view_, NULL, NULL);
792 gtk_widget_modify_base(text_view_, GTK_STATE_NORMAL, NULL);
793 gtk_widget_modify_base(text_view_, GTK_STATE_SELECTED, NULL);
794 gtk_widget_modify_text(text_view_, GTK_STATE_SELECTED, NULL);
795 gtk_widget_modify_base(text_view_, GTK_STATE_ACTIVE, NULL);
796 gtk_widget_modify_text(text_view_, GTK_STATE_ACTIVE, NULL);
798 gtk_util::UndoForceFontSize(text_view_);
799 gtk_util::UndoForceFontSize(gray_text_view_);
801 // Grab the text colors out of the style and set our tags to use them.
802 GtkStyle* style = gtk_rc_get_style(text_view_);
804 // style may be unrealized at this point, so calculate the halfway point
805 // between text[] and base[] manually instead of just using text_aa[].
806 GdkColor average_color = gtk_util::AverageColors(
807 style->text[GTK_STATE_NORMAL], style->base[GTK_STATE_NORMAL]);
809 g_object_set(faded_text_tag_, "foreground-gdk", &average_color, NULL);
810 g_object_set(normal_text_tag_, "foreground-gdk",
811 &style->text[GTK_STATE_NORMAL], NULL);
812 } else {
813 const GdkColor* background_color_ptr =
814 &LocationBarViewGtk::kBackgroundColor;
815 gtk_widget_modify_cursor(text_view_, &ui::kGdkBlack, &ui::kGdkGray);
816 gtk_widget_modify_base(text_view_, GTK_STATE_NORMAL, background_color_ptr);
818 GdkColor c;
819 // Override the selected colors so we don't leak colors from the current
820 // gtk theme into the chrome-theme.
821 c = gfx::SkColorToGdkColor(
822 theme_service_->get_active_selection_bg_color());
823 gtk_widget_modify_base(text_view_, GTK_STATE_SELECTED, &c);
825 c = gfx::SkColorToGdkColor(
826 theme_service_->get_active_selection_fg_color());
827 gtk_widget_modify_text(text_view_, GTK_STATE_SELECTED, &c);
829 c = gfx::SkColorToGdkColor(
830 theme_service_->get_inactive_selection_bg_color());
831 gtk_widget_modify_base(text_view_, GTK_STATE_ACTIVE, &c);
833 c = gfx::SkColorToGdkColor(
834 theme_service_->get_inactive_selection_fg_color());
835 gtk_widget_modify_text(text_view_, GTK_STATE_ACTIVE, &c);
837 // Until we switch to vector graphics, force the font size.
838 gtk_util::ForceFontSizePixels(text_view_, GetFont().GetFontSize());
839 gtk_util::ForceFontSizePixels(gray_text_view_, GetFont().GetFontSize());
841 g_object_set(faded_text_tag_, "foreground", kTextBaseColor, NULL);
842 g_object_set(normal_text_tag_, "foreground", "#000000", NULL);
845 AdjustVerticalAlignmentOfGrayTextView();
846 UpdateGrayTextViewColors();
849 void OmniboxViewGtk::UpdateGrayTextViewColors() {
850 GdkColor faded_text;
851 if (theme_service_->UsingNativeTheme()) {
852 GtkStyle* style = gtk_rc_get_style(gray_text_view_);
853 faded_text = gtk_util::AverageColors(
854 style->text[GTK_STATE_NORMAL], style->base[GTK_STATE_NORMAL]);
855 } else {
856 gdk_color_parse(kTextBaseColor, &faded_text);
858 gtk_widget_modify_fg(gray_text_view_, GTK_STATE_NORMAL, &faded_text);
861 void OmniboxViewGtk::HandleBeginUserAction(GtkTextBuffer* sender) {
862 OnBeforePossibleChange();
865 void OmniboxViewGtk::HandleEndUserAction(GtkTextBuffer* sender) {
866 OnAfterPossibleChange();
869 gboolean OmniboxViewGtk::HandleKeyPress(GtkWidget* widget, GdkEventKey* event) {
870 // Background of this piece of complicated code:
871 // The omnibox supports several special behaviors which may be triggered by
872 // certain key events:
873 // Tab to search - triggered by Tab key
874 // Accept input - triggered by Enter key
875 // Revert input - triggered by Escape key
877 // Because we use a GtkTextView object |text_view_| for text input, we need
878 // send all key events to |text_view_| before handling them, to make sure
879 // IME works without any problem. So here, we intercept "key-press-event"
880 // signal of |text_view_| object and call its default handler to handle the
881 // key event first.
883 // Then if the key event is one of Tab, Enter and Escape, we need to trigger
884 // the corresponding special behavior if IME did not handle it.
885 // For Escape key, if the default signal handler returns FALSE, then we know
886 // it's not handled by IME.
888 // For Tab key, as "accepts-tab" property of |text_view_| is set to FALSE,
889 // if IME did not handle it then "move-focus" signal will be emitted by the
890 // default signal handler of |text_view_|. So we can intercept "move-focus"
891 // signal of |text_view_| to know if a Tab key press event was handled by IME,
892 // and trigger Tab to search or result traversal behavior when necessary in
893 // the signal handler.
895 // But for Enter key, if IME did not handle the key event, the default signal
896 // handler will delete current selection range and insert '\n' and always
897 // return TRUE. We need to prevent |text_view_| from performing this default
898 // action if IME did not handle the key event, because we don't want the
899 // content of omnibox to be changed before triggering our special behavior.
900 // Otherwise our special behavior would not be performed correctly.
902 // But there is no way for us to prevent GtkTextView from handling the key
903 // event and performing built-in operation. So in order to achieve our goal,
904 // "insert-text" signal of |text_buffer_| object is intercepted, and
905 // following actions are done in the signal handler:
906 // - If there is only one character in inserted text, and it's '\n' or '\r',
907 // then set |enter_was_inserted_| to true.
908 // - Filter out all new line and tab characters.
910 // So if |enter_was_inserted_| is true after calling |text_view_|'s default
911 // signal handler against an Enter key press event, then we know that the
912 // Enter key press event was handled by GtkTextView rather than IME, and can
913 // perform the special behavior for Enter key safely.
915 // Now the last thing is to prevent the content of omnibox from being changed
916 // by GtkTextView when Enter key is pressed. As OnBeforePossibleChange() and
917 // OnAfterPossibleChange() will be called by GtkTextView before and after
918 // changing the content, and the content is already saved in
919 // OnBeforePossibleChange(), so if the Enter key press event was not handled
920 // by IME, it's easy to restore the content in OnAfterPossibleChange(), as if
921 // it's not changed at all.
923 GtkWidgetClass* klass = GTK_WIDGET_GET_CLASS(widget);
925 enter_was_pressed_ = event->keyval == GDK_Return ||
926 event->keyval == GDK_ISO_Enter ||
927 event->keyval == GDK_KP_Enter;
929 // Set |tab_was_pressed_| to true if it's a Tab key press event, so that our
930 // handler of "move-focus" signal can trigger Tab to search behavior when
931 // necessary.
932 tab_was_pressed_ = (event->keyval == GDK_Tab ||
933 event->keyval == GDK_ISO_Left_Tab ||
934 event->keyval == GDK_KP_Tab) &&
935 !(event->state & GDK_CONTROL_MASK);
937 shift_was_pressed_ = event->state & GDK_SHIFT_MASK;
939 delete_was_pressed_ = event->keyval == GDK_Delete ||
940 event->keyval == GDK_KP_Delete;
942 // Reset |enter_was_inserted_|, which may be set in the "insert-text" signal
943 // handler, so that we'll know if an Enter key event was handled by IME.
944 enter_was_inserted_ = false;
946 // Reset |paste_clipboard_requested_| to make sure we won't misinterpret this
947 // key input action as a paste action.
948 paste_clipboard_requested_ = false;
950 // Reset |text_changed_| before passing the key event on to the text view.
951 text_changed_ = false;
953 OnBeforePossibleChange();
954 handling_key_press_ = true;
955 content_maybe_changed_by_key_press_ = false;
957 // Call the default handler, so that IME can work as normal.
958 // New line characters will be filtered out by our "insert-text"
959 // signal handler attached to |text_buffer_| object.
960 gboolean result = klass->key_press_event(widget, event);
962 handling_key_press_ = false;
963 if (content_maybe_changed_by_key_press_)
964 OnAfterPossibleChange();
966 // Set |tab_was_pressed_| to false, to make sure Tab to search behavior can
967 // only be triggered by pressing Tab key.
968 tab_was_pressed_ = false;
970 if (enter_was_pressed_ && enter_was_inserted_) {
971 bool alt_held = (event->state & GDK_MOD1_MASK);
972 model()->AcceptInput(alt_held ? NEW_FOREGROUND_TAB : CURRENT_TAB, false);
973 result = TRUE;
974 } else if (!result && event->keyval == GDK_Escape &&
975 (event->state & gtk_accelerator_get_default_mod_mask()) == 0) {
976 // We can handle the Escape key if |text_view_| did not handle it.
977 // If it's not handled by us, then we need to propagate it up to the parent
978 // widgets, so that Escape accelerator can still work.
979 result = model()->OnEscapeKeyPressed();
980 } else if (event->keyval == GDK_Control_L || event->keyval == GDK_Control_R) {
981 // Omnibox2 can switch its contents while pressing a control key. To switch
982 // the contents of omnibox2, we notify the OmniboxEditModel class when the
983 // control-key state is changed.
984 model()->OnControlKeyChanged(true);
985 } else if (!text_changed_ && event->keyval == GDK_Delete &&
986 event->state & GDK_SHIFT_MASK) {
987 // If shift+del didn't change the text, we let this delete an entry from
988 // the popup. We can't check to see if the IME handled it because even if
989 // nothing is selected, the IME or the TextView still report handling it.
990 if (model()->popup_model()->IsOpen())
991 model()->popup_model()->TryDeletingCurrentItem();
994 // Set |enter_was_pressed_| to false, to make sure OnAfterPossibleChange() can
995 // act as normal for changes made by other events.
996 enter_was_pressed_ = false;
998 // If the key event is not handled by |text_view_| or us, then we need to
999 // propagate the key event up to parent widgets by returning FALSE.
1000 // In this case we need to stop the signal emission explicitly to prevent the
1001 // default "key-press-event" handler of |text_view_| from being called again.
1002 if (!result) {
1003 static guint signal_id =
1004 g_signal_lookup("key-press-event", GTK_TYPE_WIDGET);
1005 g_signal_stop_emission(widget, signal_id, 0);
1008 return result;
1011 gboolean OmniboxViewGtk::HandleKeyRelease(GtkWidget* widget,
1012 GdkEventKey* event) {
1013 // Omnibox2 can switch its contents while pressing a control key. To switch
1014 // the contents of omnibox2, we notify the OmniboxEditModel class when the
1015 // control-key state is changed.
1016 if (event->keyval == GDK_Control_L || event->keyval == GDK_Control_R) {
1017 // Round trip to query the control state after the release. This allows
1018 // you to release one control key while still holding another control key.
1019 GdkDisplay* display = gdk_window_get_display(event->window);
1020 GdkModifierType mod;
1021 gdk_display_get_pointer(display, NULL, NULL, NULL, &mod);
1022 if (!(mod & GDK_CONTROL_MASK))
1023 model()->OnControlKeyChanged(false);
1026 // Even though we handled the press ourselves, let GtkTextView handle the
1027 // release. It shouldn't do anything particularly interesting, but it will
1028 // handle the IME work for us.
1029 return FALSE; // Propagate into GtkTextView.
1032 gboolean OmniboxViewGtk::HandleViewButtonPress(GtkWidget* sender,
1033 GdkEventButton* event) {
1034 // We don't need to care about double and triple clicks.
1035 if (event->type != GDK_BUTTON_PRESS)
1036 return FALSE;
1038 DCHECK(text_view_);
1040 // Restore caret visibility whenever the user clicks in the omnibox in a way
1041 // that would give it focus. We must handle this case separately here because
1042 // if the omnibox currently has invisible focus, the mouse event won't trigger
1043 // either SetFocus() or OmniboxEditModel::OnSetFocus().
1044 if (event->button == 1 || event->button == 2)
1045 model()->SetCaretVisibility(true);
1047 if (event->button == 1) {
1048 button_1_pressed_ = true;
1050 // Button press event may change the selection, we need to record the change
1051 // and report it to model() later when button is released.
1052 OnBeforePossibleChange();
1053 } else if (event->button == 2) {
1054 // GtkTextView pastes PRIMARY selection with middle click.
1055 // We can't call model()->on_paste_replacing_all() here, because the actual
1056 // paste clipboard action may not be performed if the clipboard is empty.
1057 paste_clipboard_requested_ = true;
1059 return FALSE;
1062 gboolean OmniboxViewGtk::HandleViewButtonRelease(GtkWidget* sender,
1063 GdkEventButton* event) {
1064 if (event->button != 1)
1065 return FALSE;
1067 bool button_1_was_pressed = button_1_pressed_;
1068 button_1_pressed_ = false;
1070 DCHECK(text_view_);
1072 // Call the GtkTextView default handler, ignoring the fact that it will
1073 // likely have told us to stop propagating. We want to handle selection.
1074 GtkWidgetClass* klass = GTK_WIDGET_GET_CLASS(text_view_);
1075 klass->button_release_event(text_view_, event);
1077 // Inform model() about possible text selection change. We may get a button
1078 // release with no press (e.g. if the user clicks in the omnibox to dismiss a
1079 // bubble).
1080 if (button_1_was_pressed)
1081 OnAfterPossibleChange();
1083 return TRUE; // Don't continue, we called the default handler already.
1086 gboolean OmniboxViewGtk::HandleViewFocusIn(GtkWidget* sender,
1087 GdkEventFocus* event) {
1088 DCHECK(text_view_);
1089 update_popup_without_focus_ = false;
1091 GdkModifierType modifiers;
1092 GdkWindow* gdk_window = gtk_widget_get_window(text_view_);
1093 gdk_window_get_pointer(gdk_window, NULL, NULL, &modifiers);
1094 model()->OnSetFocus((modifiers & GDK_CONTROL_MASK) != 0);
1095 controller()->OnSetFocus();
1096 // TODO(deanm): Some keyword hit business, etc here.
1098 g_signal_connect(
1099 gdk_keymap_get_for_display(gtk_widget_get_display(text_view_)),
1100 "direction-changed",
1101 G_CALLBACK(&HandleKeymapDirectionChangedThunk), this);
1103 AdjustTextJustification();
1105 return FALSE; // Continue propagation.
1108 gboolean OmniboxViewGtk::HandleViewFocusOut(GtkWidget* sender,
1109 GdkEventFocus* event) {
1110 DCHECK(text_view_);
1111 GtkWidget* view_getting_focus = NULL;
1112 GtkWindow* toplevel = platform_util::GetTopLevel(sender);
1113 if (gtk_window_is_active(toplevel))
1114 view_getting_focus = going_to_focus_;
1116 // This must be invoked before ClosePopup.
1117 model()->OnWillKillFocus(view_getting_focus);
1119 // Close the popup.
1120 CloseOmniboxPopup();
1121 // Tell the model to reset itself.
1122 model()->OnKillFocus();
1124 g_signal_handlers_disconnect_by_func(
1125 gdk_keymap_get_for_display(gtk_widget_get_display(text_view_)),
1126 reinterpret_cast<gpointer>(&HandleKeymapDirectionChangedThunk), this);
1128 return FALSE; // Pass the event on to the GtkTextView.
1131 void OmniboxViewGtk::HandleViewMoveCursor(
1132 GtkWidget* sender,
1133 GtkMovementStep step,
1134 gint count,
1135 gboolean extend_selection) {
1136 DCHECK(text_view_);
1137 GtkTextIter sel_start, sel_end;
1138 gboolean has_selection =
1139 gtk_text_buffer_get_selection_bounds(text_buffer_, &sel_start, &sel_end);
1140 bool handled = false;
1142 if (step == GTK_MOVEMENT_VISUAL_POSITIONS && !extend_selection &&
1143 (count == 1 || count == -1)) {
1144 // We need to take the content direction into account when handling cursor
1145 // movement, because the behavior of Left and Right key will be inverted if
1146 // the direction is RTL. Although we should check the direction around the
1147 // input caret, it's much simpler and good enough to check whole content's
1148 // direction.
1149 PangoDirection content_dir = GetContentDirection();
1150 gint count_towards_end = content_dir == PANGO_DIRECTION_RTL ? -1 : 1;
1152 // We want the GtkEntry behavior when you move the cursor while you have a
1153 // selection. GtkTextView just drops the selection and moves the cursor,
1154 // but instead we want to move the cursor to the appropiate end of the
1155 // selection.
1156 if (has_selection) {
1157 // We have a selection and start / end are in ascending order.
1158 // Cursor placement will remove the selection, so we need inform
1159 // model() about this change by
1160 // calling On{Before|After}PossibleChange() methods.
1161 OnBeforePossibleChange();
1162 gtk_text_buffer_place_cursor(
1163 text_buffer_, count == count_towards_end ? &sel_end : &sel_start);
1164 OnAfterPossibleChange();
1165 handled = true;
1166 } else if (count == count_towards_end && !IsCaretAtEnd()) {
1167 handled = model()->CommitSuggestedText();
1169 } else if (step == GTK_MOVEMENT_PAGES) { // Page up and down.
1170 // Multiply by count for the direction (if we move too much that's ok).
1171 model()->OnUpOrDownKeyPressed(model()->result().size() * count);
1172 handled = true;
1173 } else if (step == GTK_MOVEMENT_DISPLAY_LINES) { // Arrow up and down.
1174 model()->OnUpOrDownKeyPressed(count);
1175 handled = true;
1178 if (!handled) {
1179 // Cursor movement may change the selection, we need to record the change
1180 // and report it to model().
1181 if (has_selection || extend_selection)
1182 OnBeforePossibleChange();
1184 // Propagate into GtkTextView
1185 GtkTextViewClass* klass = GTK_TEXT_VIEW_GET_CLASS(text_view_);
1186 klass->move_cursor(GTK_TEXT_VIEW(text_view_), step, count,
1187 extend_selection);
1189 if (has_selection || extend_selection)
1190 OnAfterPossibleChange();
1193 // move-cursor doesn't use a signal accumulator on the return value (it
1194 // just ignores then), so we have to stop the propagation.
1195 static guint signal_id = g_signal_lookup("move-cursor", GTK_TYPE_TEXT_VIEW);
1196 g_signal_stop_emission(text_view_, signal_id, 0);
1199 void OmniboxViewGtk::HandleViewSizeRequest(GtkWidget* sender,
1200 GtkRequisition* req) {
1201 // Don't force a minimum width, but use the font-relative height. This is a
1202 // run-first handler, so the default handler was already called.
1203 req->width = 1;
1206 void OmniboxViewGtk::HandlePopupMenuDeactivate(GtkWidget* sender) {
1207 // When the context menu appears, |text_view_|'s focus is lost. After an item
1208 // is activated, the focus comes back to |text_view_|, but only after the
1209 // check in UpdatePopup(). We set this flag to make UpdatePopup() aware that
1210 // it will be receiving focus again.
1211 if (!model()->has_focus())
1212 update_popup_without_focus_ = true;
1215 void OmniboxViewGtk::HandlePopulatePopup(GtkWidget* sender, GtkMenu* menu) {
1216 GtkWidget* separator = gtk_separator_menu_item_new();
1217 gtk_menu_shell_append(GTK_MENU_SHELL(menu), separator);
1218 gtk_widget_show(separator);
1220 // Paste and Go menu item.
1221 GtkClipboard* x_clipboard = gtk_clipboard_get(GDK_SELECTION_CLIPBOARD);
1222 gchar* text = gtk_clipboard_wait_for_text(x_clipboard);
1223 sanitized_text_for_paste_and_go_ = text ?
1224 StripJavascriptSchemas(
1225 CollapseWhitespace(base::UTF8ToUTF16(text), true)) :
1226 base::string16();
1227 g_free(text);
1228 GtkWidget* paste_and_go_menuitem = gtk_menu_item_new_with_mnemonic(
1229 ui::ConvertAcceleratorsFromWindowsStyle(l10n_util::GetStringUTF8(
1230 model()->IsPasteAndSearch(sanitized_text_for_paste_and_go_) ?
1231 IDS_PASTE_AND_SEARCH : IDS_PASTE_AND_GO)).c_str());
1232 // Detect the stock Paste menu item by searching for the stock label
1233 // GTK_STOCK_PASTE. If we don't find it, the Paste and Go item will be
1234 // appended at the end of the popup menu.
1235 gtk_menu_shell_insert(GTK_MENU_SHELL(menu), paste_and_go_menuitem,
1236 GetPopupMenuIndexForStockLabel(GTK_STOCK_PASTE, menu));
1237 g_signal_connect(paste_and_go_menuitem, "activate",
1238 G_CALLBACK(HandlePasteAndGoThunk), this);
1239 gtk_widget_set_sensitive(
1240 paste_and_go_menuitem,
1241 model()->CanPasteAndGo(sanitized_text_for_paste_and_go_));
1242 gtk_widget_show(paste_and_go_menuitem);
1244 // Show URL menu item.
1245 if (chrome::IsQueryExtractionEnabled()) {
1246 GtkWidget* show_url_menuitem = gtk_menu_item_new_with_mnemonic(
1247 ui::ConvertAcceleratorsFromWindowsStyle(
1248 l10n_util::GetStringUTF8(IDS_SHOW_URL)).c_str());
1249 gtk_menu_shell_append(GTK_MENU_SHELL(menu), show_url_menuitem);
1250 g_signal_connect(show_url_menuitem, "activate",
1251 G_CALLBACK(HandleShowURLThunk), this);
1252 gtk_widget_set_sensitive(
1253 show_url_menuitem,
1254 controller()->GetToolbarModel()->WouldReplaceURL());
1255 gtk_widget_show(show_url_menuitem);
1258 // Edit Search Engines menu item.
1259 GtkWidget* edit_search_engines_menuitem = gtk_menu_item_new_with_mnemonic(
1260 ui::ConvertAcceleratorsFromWindowsStyle(
1261 l10n_util::GetStringUTF8(IDS_EDIT_SEARCH_ENGINES)).c_str());
1262 gtk_menu_shell_append(GTK_MENU_SHELL(menu), edit_search_engines_menuitem);
1263 g_signal_connect(edit_search_engines_menuitem, "activate",
1264 G_CALLBACK(HandleEditSearchEnginesThunk), this);
1265 gtk_widget_set_sensitive(
1266 edit_search_engines_menuitem,
1267 command_updater()->IsCommandEnabled(IDC_EDIT_SEARCH_ENGINES));
1268 gtk_widget_show(edit_search_engines_menuitem);
1270 g_signal_connect(menu, "deactivate",
1271 G_CALLBACK(HandlePopupMenuDeactivateThunk), this);
1274 void OmniboxViewGtk::HandlePasteAndGo(GtkWidget* sender) {
1275 model()->PasteAndGo(sanitized_text_for_paste_and_go_);
1278 void OmniboxViewGtk::HandleEditSearchEngines(GtkWidget* sender) {
1279 command_updater()->ExecuteCommand(IDC_EDIT_SEARCH_ENGINES);
1282 void OmniboxViewGtk::HandleShowURL(GtkWidget* sender) {
1283 ShowURL();
1286 void OmniboxViewGtk::HandleMarkSet(GtkTextBuffer* buffer,
1287 GtkTextIter* location,
1288 GtkTextMark* mark) {
1289 if (!text_buffer_ || buffer != text_buffer_)
1290 return;
1292 if (mark != gtk_text_buffer_get_insert(text_buffer_) &&
1293 mark != gtk_text_buffer_get_selection_bound(text_buffer_)) {
1294 return;
1297 // If we are here, that means the user may be changing the selection
1298 selection_suggested_ = false;
1300 // Get the currently-selected text, if there is any.
1301 std::string new_selected_text = GetSelectedText();
1303 // If we had some text selected earlier but it's no longer highlighted, we
1304 // might need to save it now...
1305 if (!selected_text_.empty() && new_selected_text.empty()) {
1306 // ... but only if we currently own the selection. We want to manually
1307 // update the selection when the text is unhighlighted because the user
1308 // clicked in a blank area of the text view, but not when it's unhighlighted
1309 // because another client or widget took the selection. (This handler gets
1310 // called before the default handler, so as long as nobody else took the
1311 // selection, the text buffer still owns it even if GTK is about to take it
1312 // away in the default handler.)
1313 GtkClipboard* clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
1314 if (gtk_clipboard_get_owner(clipboard) == G_OBJECT(text_buffer_))
1315 SavePrimarySelection(selected_text_);
1318 selected_text_ = new_selected_text;
1321 // Override the primary selection the text buffer has set. This has to happen
1322 // after the default handler for the "mark-set" signal.
1323 void OmniboxViewGtk::HandleMarkSetAfter(GtkTextBuffer* buffer,
1324 GtkTextIter* location,
1325 GtkTextMark* mark) {
1326 if (!text_buffer_ || buffer != text_buffer_)
1327 return;
1329 // We should only update primary selection when the user changes the selection
1330 // range.
1331 if (mark != gtk_text_buffer_get_insert(text_buffer_) &&
1332 mark != gtk_text_buffer_get_selection_bound(text_buffer_)) {
1333 return;
1336 UpdatePrimarySelectionIfValidURL();
1339 // Just use the default behavior for DnD, except if the drop can be a PasteAndGo
1340 // then override.
1341 void OmniboxViewGtk::HandleDragDataReceived(GtkWidget* sender,
1342 GdkDragContext* context,
1343 gint x,
1344 gint y,
1345 GtkSelectionData* selection_data,
1346 guint target_type,
1347 guint time) {
1348 DCHECK(text_view_);
1350 // Reset |paste_clipboard_requested_| to make sure we won't misinterpret this
1351 // drop action as a paste action.
1352 paste_clipboard_requested_ = false;
1354 // Don't try to PasteAndGo on drops originating from this omnibox. However, do
1355 // allow default behavior for such drags.
1356 if (gdk_drag_context_get_source_window(context) ==
1357 gtk_widget_get_window(text_view_))
1358 return;
1360 guchar* text = gtk_selection_data_get_text(selection_data);
1361 if (!text)
1362 return;
1364 base::string16 possible_url =
1365 base::UTF8ToUTF16(reinterpret_cast<char*>(text));
1366 g_free(text);
1367 if (OnPerformDropImpl(possible_url)) {
1368 gtk_drag_finish(context, TRUE, FALSE, time);
1370 static guint signal_id =
1371 g_signal_lookup("drag-data-received", GTK_TYPE_WIDGET);
1372 g_signal_stop_emission(text_view_, signal_id, 0);
1376 void OmniboxViewGtk::HandleDragDataGet(GtkWidget* widget,
1377 GdkDragContext* context,
1378 GtkSelectionData* selection_data,
1379 guint target_type,
1380 guint time) {
1381 DCHECK(text_view_);
1383 switch (target_type) {
1384 case GTK_TEXT_BUFFER_TARGET_INFO_TEXT: {
1385 gtk_selection_data_set_text(selection_data, dragged_text_.c_str(), -1);
1386 break;
1388 case ui::CHROME_NAMED_URL: {
1389 WebContents* current_tab = controller()->GetWebContents();
1390 base::string16 tab_title = current_tab->GetTitle();
1391 // Pass an empty string if user has edited the URL.
1392 if (current_tab->GetURL().spec() != dragged_text_)
1393 tab_title = base::string16();
1394 ui::WriteURLWithName(selection_data, GURL(dragged_text_),
1395 tab_title, target_type);
1396 break;
1401 void OmniboxViewGtk::HandleDragBegin(GtkWidget* widget,
1402 GdkDragContext* context) {
1403 base::string16 text = base::UTF8ToUTF16(GetSelectedText());
1405 if (text.empty())
1406 return;
1408 // Use AdjustTextForCopy to make sure we prefix the text with 'http://'.
1409 CharRange selection = GetSelection();
1410 GURL url;
1411 bool write_url;
1412 model()->AdjustTextForCopy(selection.selection_min(), IsSelectAll(), &text,
1413 &url, &write_url);
1414 if (write_url) {
1415 selected_text_ = base::UTF16ToUTF8(text);
1416 GtkTargetList* copy_targets =
1417 gtk_text_buffer_get_copy_target_list(text_buffer_);
1418 gtk_target_list_add(copy_targets,
1419 ui::GetAtomForTarget(ui::CHROME_NAMED_URL),
1420 GTK_TARGET_SAME_APP, ui::CHROME_NAMED_URL);
1422 dragged_text_ = selected_text_;
1425 void OmniboxViewGtk::HandleDragEnd(GtkWidget* widget,
1426 GdkDragContext* context) {
1427 GdkAtom atom = ui::GetAtomForTarget(ui::CHROME_NAMED_URL);
1428 GtkTargetList* copy_targets =
1429 gtk_text_buffer_get_copy_target_list(text_buffer_);
1430 gtk_target_list_remove(copy_targets, atom);
1431 dragged_text_.clear();
1434 void OmniboxViewGtk::HandleInsertText(GtkTextBuffer* buffer,
1435 GtkTextIter* location,
1436 const gchar* text,
1437 gint len) {
1438 base::string16 filtered_text;
1439 filtered_text.reserve(len);
1441 // Filter out new line and tab characters.
1442 // |text| is guaranteed to be a valid UTF-8 string, so we don't need to
1443 // validate it here.
1445 // If there was only a single character, then it might be generated by a key
1446 // event. In this case, we save the single character to help our
1447 // "key-press-event" signal handler distinguish if an Enter key event is
1448 // handled by IME or not.
1449 if (len == 1 && (text[0] == '\n' || text[0] == '\r'))
1450 enter_was_inserted_ = true;
1452 for (const gchar* p = text; *p && (p - text) < len;
1453 p = g_utf8_next_char(p)) {
1454 gunichar c = g_utf8_get_char(p);
1456 // 0x200B is Zero Width Space, which is inserted just before the gray text
1457 // anchor for working around the GtkTextView's misalignment bug.
1458 // This character might be captured and inserted into the content by undo
1459 // manager, so we need to filter it out here.
1460 if (c != 0x200B)
1461 base::WriteUnicodeCharacter(c, &filtered_text);
1464 if (model()->is_pasting()) {
1465 // If the user is pasting all-whitespace, paste a single space
1466 // rather than nothing, since pasting nothing feels broken.
1467 filtered_text = CollapseWhitespace(filtered_text, true);
1468 filtered_text = filtered_text.empty() ? base::ASCIIToUTF16(" ") :
1469 StripJavascriptSchemas(filtered_text);
1472 if (!filtered_text.empty()) {
1473 // Avoid inserting the text after the gray text anchor.
1474 ValidateTextBufferIter(location);
1476 // Call the default handler to insert filtered text.
1477 GtkTextBufferClass* klass = GTK_TEXT_BUFFER_GET_CLASS(buffer);
1478 std::string utf8_text = base::UTF16ToUTF8(filtered_text);
1479 klass->insert_text(buffer, location, utf8_text.data(),
1480 static_cast<gint>(utf8_text.length()));
1483 // Stop propagating the signal emission to prevent the default handler from
1484 // being called again.
1485 static guint signal_id = g_signal_lookup("insert-text", GTK_TYPE_TEXT_BUFFER);
1486 g_signal_stop_emission(buffer, signal_id, 0);
1489 void OmniboxViewGtk::HandleBackSpace(GtkWidget* sender) {
1490 // Checks if it's currently in keyword search mode.
1491 if (model()->is_keyword_hint() || model()->keyword().empty())
1492 return; // Propgate into GtkTextView.
1494 DCHECK(text_view_);
1496 GtkTextIter sel_start, sel_end;
1497 // Checks if there is some text selected.
1498 if (gtk_text_buffer_get_selection_bounds(text_buffer_, &sel_start, &sel_end))
1499 return; // Propgate into GtkTextView.
1501 GtkTextIter start;
1502 gtk_text_buffer_get_start_iter(text_buffer_, &start);
1504 if (!gtk_text_iter_equal(&start, &sel_start))
1505 return; // Propgate into GtkTextView.
1507 // We're showing a keyword and the user pressed backspace at the beginning
1508 // of the text. Delete the selected keyword.
1509 model()->ClearKeyword(GetText());
1511 // Stop propagating the signal emission into GtkTextView.
1512 static guint signal_id = g_signal_lookup("backspace", GTK_TYPE_TEXT_VIEW);
1513 g_signal_stop_emission(text_view_, signal_id, 0);
1516 void OmniboxViewGtk::HandleViewMoveFocus(GtkWidget* widget,
1517 GtkDirectionType direction) {
1518 if (!tab_was_pressed_)
1519 return;
1521 // If special behavior is triggered, then stop the signal emission to
1522 // prevent the focus from being moved.
1523 bool handled = false;
1525 // Trigger Tab to search behavior only when Tab key is pressed.
1526 if (model()->is_keyword_hint() && !shift_was_pressed_) {
1527 handled = model()->AcceptKeyword(ENTERED_KEYWORD_MODE_VIA_TAB);
1528 } else if (model()->popup_model()->IsOpen()) {
1529 if (shift_was_pressed_ &&
1530 model()->popup_model()->selected_line_state() ==
1531 OmniboxPopupModel::KEYWORD)
1532 model()->ClearKeyword(GetText());
1533 else
1534 model()->OnUpOrDownKeyPressed(shift_was_pressed_ ? -1 : 1);
1536 handled = true;
1539 if (supports_pre_edit_ && !handled && !pre_edit_.empty())
1540 handled = true;
1542 if (!handled && gtk_widget_get_visible(gray_text_view_))
1543 handled = model()->CommitSuggestedText();
1545 if (handled) {
1546 static guint signal_id = g_signal_lookup("move-focus", GTK_TYPE_WIDGET);
1547 g_signal_stop_emission(widget, signal_id, 0);
1551 void OmniboxViewGtk::HandleCopyClipboard(GtkWidget* sender) {
1552 HandleCopyOrCutClipboard(true);
1555 void OmniboxViewGtk::HandleCutClipboard(GtkWidget* sender) {
1556 HandleCopyOrCutClipboard(false);
1559 void OmniboxViewGtk::HandleCopyOrCutClipboard(bool copy) {
1560 DCHECK(text_view_);
1562 // On copy or cut, we manually update the PRIMARY selection to contain the
1563 // highlighted text. This matches Firefox -- we highlight the URL but don't
1564 // update PRIMARY on Ctrl-L, so Ctrl-L, Ctrl-C and then middle-click is a
1565 // convenient way to paste the current URL somewhere.
1566 if (!gtk_text_buffer_get_has_selection(text_buffer_))
1567 return;
1569 GtkClipboard* clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
1570 DCHECK(clipboard);
1572 CharRange selection = GetSelection();
1573 GURL url;
1574 base::string16 text(base::UTF8ToUTF16(GetSelectedText()));
1575 bool write_url;
1576 model()->AdjustTextForCopy(selection.selection_min(), IsSelectAll(), &text,
1577 &url, &write_url);
1579 if (IsSelectAll())
1580 UMA_HISTOGRAM_COUNTS(OmniboxEditModel::kCutOrCopyAllTextHistogram, 1);
1582 // On other platforms we write |text| to the clipboard regardless of
1583 // |write_url|. We don't need to do that here because we fall through to
1584 // the default signal handlers.
1585 if (write_url) {
1586 BookmarkNodeData data;
1587 data.ReadFromTuple(url, text);
1588 data.WriteToClipboard(ui::CLIPBOARD_TYPE_COPY_PASTE);
1589 SetSelectedRange(selection);
1591 // Stop propagating the signal.
1592 static guint copy_signal_id =
1593 g_signal_lookup("copy-clipboard", GTK_TYPE_TEXT_VIEW);
1594 static guint cut_signal_id =
1595 g_signal_lookup("cut-clipboard", GTK_TYPE_TEXT_VIEW);
1596 g_signal_stop_emission(text_view_,
1597 copy ? copy_signal_id : cut_signal_id,
1600 if (!copy && gtk_text_view_get_editable(GTK_TEXT_VIEW(text_view_)))
1601 gtk_text_buffer_delete_selection(text_buffer_, true, true);
1604 OwnPrimarySelection(base::UTF16ToUTF8(text));
1607 int OmniboxViewGtk::GetOmniboxTextLength() const {
1608 GtkTextIter end;
1609 gtk_text_buffer_get_iter_at_mark(text_buffer_, &end, gray_text_mark_);
1610 if (supports_pre_edit_) {
1611 // We need to count the length of the text being composed, because we treat
1612 // it as part of the content in GetText().
1613 return gtk_text_iter_get_offset(&end) + pre_edit_.size();
1615 return gtk_text_iter_get_offset(&end);
1618 void OmniboxViewGtk::EmphasizeURLComponents() {
1619 if (supports_pre_edit_) {
1620 // We can't change the text style easily, if the pre-edit string (the text
1621 // being composed by the input method) is not empty, which is not treated as
1622 // a part of the text content inside GtkTextView. And it's ok to simply
1623 // return in this case, as this method will be called again when the
1624 // pre-edit string gets committed.
1625 if (pre_edit_.size()) {
1626 strikethrough_ = CharRange();
1627 return;
1630 // See whether the contents are a URL with a non-empty host portion, which we
1631 // should emphasize. To check for a URL, rather than using the type returned
1632 // by Parse(), ask the model, which will check the desired page transition for
1633 // this input. This can tell us whether an UNKNOWN input string is going to
1634 // be treated as a search or a navigation, and is the same method the Paste
1635 // And Go system uses.
1636 url_parse::Component scheme, host;
1637 base::string16 text(GetText());
1638 AutocompleteInput::ParseForEmphasizeComponents(text, &scheme, &host);
1640 // Set the baseline emphasis.
1641 GtkTextIter start, end;
1642 GetTextBufferBounds(&start, &end);
1643 gtk_text_buffer_remove_all_tags(text_buffer_, &start, &end);
1644 bool grey_out_url = text.substr(scheme.begin, scheme.len) ==
1645 base::UTF8ToUTF16(extensions::kExtensionScheme);
1646 bool grey_base = model()->CurrentTextIsURL() &&
1647 (host.is_nonempty() || grey_out_url);
1648 gtk_text_buffer_apply_tag(
1649 text_buffer_, grey_base ? faded_text_tag_ : normal_text_tag_ , &start,
1650 &end);
1652 if (grey_base && !grey_out_url) {
1653 // We've found a host name, give it more emphasis.
1654 gtk_text_buffer_get_iter_at_line_index(
1655 text_buffer_, &start, 0, GetUTF8Offset(text, host.begin));
1656 gtk_text_buffer_get_iter_at_line_index(
1657 text_buffer_, &end, 0, GetUTF8Offset(text, host.end()));
1658 gtk_text_buffer_apply_tag(text_buffer_, normal_text_tag_, &start, &end);
1661 strikethrough_ = CharRange();
1662 // Emphasize the scheme for security UI display purposes (if necessary).
1663 if (!model()->user_input_in_progress() && model()->CurrentTextIsURL() &&
1664 scheme.is_nonempty() && (security_level_ != ToolbarModel::NONE)) {
1665 CharRange scheme_range = CharRange(GetUTF8Offset(text, scheme.begin),
1666 GetUTF8Offset(text, scheme.end()));
1667 ItersFromCharRange(scheme_range, &start, &end);
1669 if (security_level_ == ToolbarModel::SECURITY_ERROR) {
1670 strikethrough_ = scheme_range;
1671 // When we draw the strikethrough, we don't want to include the ':' at the
1672 // end of the scheme.
1673 strikethrough_.cp_max--;
1675 gtk_text_buffer_apply_tag(text_buffer_, security_error_scheme_tag_,
1676 &start, &end);
1677 } else if (security_level_ == ToolbarModel::SECURITY_WARNING) {
1678 gtk_text_buffer_apply_tag(text_buffer_, faded_text_tag_, &start, &end);
1679 } else {
1680 gtk_text_buffer_apply_tag(text_buffer_, secure_scheme_tag_, &start, &end);
1685 bool OmniboxViewGtk::OnPerformDropImpl(const base::string16& text) {
1686 base::string16 sanitized_string(StripJavascriptSchemas(
1687 CollapseWhitespace(text, true)));
1688 if (model()->CanPasteAndGo(sanitized_string)) {
1689 model()->PasteAndGo(sanitized_string);
1690 return true;
1693 return false;
1696 gfx::Font OmniboxViewGtk::GetFont() {
1697 if (!theme_service_->UsingNativeTheme()) {
1698 return gfx::Font(
1699 ui::ResourceBundle::GetSharedInstance().GetFont(
1700 ui::ResourceBundle::BaseFont).GetFontName(),
1701 browser_defaults::kOmniboxFontPixelSize);
1704 // If we haven't initialized the text view yet, just create a temporary one
1705 // whose style we can grab.
1706 GtkWidget* widget = text_view_ ? text_view_ : gtk_text_view_new();
1707 GtkStyle* gtk_style = gtk_widget_get_style(widget);
1708 GtkRcStyle* rc_style = gtk_widget_get_modifier_style(widget);
1709 gfx::Font font(
1710 (rc_style && rc_style->font_desc) ?
1711 rc_style->font_desc : gtk_style->font_desc);
1712 if (!text_view_)
1713 g_object_unref(g_object_ref_sink(widget));
1714 return font;
1717 void OmniboxViewGtk::OwnPrimarySelection(const std::string& text) {
1718 primary_selection_text_ = text;
1720 GtkTargetList* list = gtk_target_list_new(NULL, 0);
1721 gtk_target_list_add_text_targets(list, 0);
1722 gint len;
1723 GtkTargetEntry* entries = gtk_target_table_new_from_list(list, &len);
1725 // When |text_buffer_| is destroyed, it will clear the clipboard, hence
1726 // we needn't worry about calling gtk_clipboard_clear().
1727 gtk_clipboard_set_with_owner(gtk_clipboard_get(GDK_SELECTION_PRIMARY),
1728 entries, len,
1729 ClipboardGetSelectionThunk,
1730 ClipboardSelectionCleared,
1731 G_OBJECT(text_buffer_));
1733 gtk_target_list_unref(list);
1734 gtk_target_table_free(entries, len);
1737 void OmniboxViewGtk::HandlePasteClipboard(GtkWidget* sender) {
1738 // We can't call model()->on_paste_replacing_all() here, because the actual
1739 // paste clipboard action may not be performed if the clipboard is empty.
1740 paste_clipboard_requested_ = true;
1743 gfx::Rect OmniboxViewGtk::WindowBoundsFromIters(GtkTextIter* iter1,
1744 GtkTextIter* iter2) {
1745 GdkRectangle start_location, end_location;
1746 GtkTextView* text_view = GTK_TEXT_VIEW(text_view_);
1747 gtk_text_view_get_iter_location(text_view, iter1, &start_location);
1748 gtk_text_view_get_iter_location(text_view, iter2, &end_location);
1750 gint x1, x2, y1, y2;
1751 gtk_text_view_buffer_to_window_coords(text_view, GTK_TEXT_WINDOW_WIDGET,
1752 start_location.x, start_location.y,
1753 &x1, &y1);
1754 gtk_text_view_buffer_to_window_coords(text_view, GTK_TEXT_WINDOW_WIDGET,
1755 end_location.x + end_location.width,
1756 end_location.y + end_location.height,
1757 &x2, &y2);
1759 return gfx::Rect(x1, y1, x2 - x1, y2 - y1);
1762 gboolean OmniboxViewGtk::HandleExposeEvent(GtkWidget* sender,
1763 GdkEventExpose* expose) {
1764 if (strikethrough_.cp_min >= strikethrough_.cp_max)
1765 return FALSE;
1766 DCHECK(text_view_);
1768 gfx::Rect expose_rect(expose->area);
1770 GtkTextIter iter_min, iter_max;
1771 ItersFromCharRange(strikethrough_, &iter_min, &iter_max);
1772 gfx::Rect strikethrough_rect = WindowBoundsFromIters(&iter_min, &iter_max);
1774 if (!expose_rect.Intersects(strikethrough_rect))
1775 return FALSE;
1777 // Finally, draw.
1778 cairo_t* cr = gdk_cairo_create(GDK_DRAWABLE(expose->window));
1779 cairo_rectangle(cr, expose_rect.x(), expose_rect.y(),
1780 expose_rect.width(), expose_rect.height());
1781 cairo_clip(cr);
1783 // TODO(estade): we probably shouldn't draw the strikethrough on selected
1784 // text. I started to do this, but it was way more effort than it seemed
1785 // worth.
1786 strikethrough_rect.Inset(kStrikethroughStrokeWidth,
1787 kStrikethroughStrokeWidth);
1788 cairo_set_source_rgb(cr, kStrikethroughStrokeRed, 0.0, 0.0);
1789 cairo_set_line_width(cr, kStrikethroughStrokeWidth);
1790 cairo_set_line_cap(cr, CAIRO_LINE_CAP_ROUND);
1791 cairo_move_to(cr, strikethrough_rect.x(), strikethrough_rect.bottom());
1792 cairo_line_to(cr, strikethrough_rect.right(), strikethrough_rect.y());
1793 cairo_stroke(cr);
1794 cairo_destroy(cr);
1796 return FALSE;
1799 void OmniboxViewGtk::SelectAllInternal(bool reversed,
1800 bool update_primary_selection) {
1801 GtkTextIter start, end;
1802 if (reversed) {
1803 GetTextBufferBounds(&end, &start);
1804 } else {
1805 GetTextBufferBounds(&start, &end);
1807 if (!update_primary_selection)
1808 StartUpdatingHighlightedText();
1809 gtk_text_buffer_select_range(text_buffer_, &start, &end);
1810 if (!update_primary_selection)
1811 FinishUpdatingHighlightedText();
1814 void OmniboxViewGtk::StartUpdatingHighlightedText() {
1815 if (gtk_widget_get_realized(text_view_)) {
1816 GtkClipboard* clipboard =
1817 gtk_widget_get_clipboard(text_view_, GDK_SELECTION_PRIMARY);
1818 DCHECK(clipboard);
1819 if (clipboard)
1820 gtk_text_buffer_remove_selection_clipboard(text_buffer_, clipboard);
1822 g_signal_handler_block(text_buffer_, mark_set_handler_id_);
1823 g_signal_handler_block(text_buffer_, mark_set_handler_id2_);
1826 void OmniboxViewGtk::FinishUpdatingHighlightedText() {
1827 if (gtk_widget_get_realized(text_view_)) {
1828 GtkClipboard* clipboard =
1829 gtk_widget_get_clipboard(text_view_, GDK_SELECTION_PRIMARY);
1830 DCHECK(clipboard);
1831 if (clipboard)
1832 gtk_text_buffer_add_selection_clipboard(text_buffer_, clipboard);
1834 g_signal_handler_unblock(text_buffer_, mark_set_handler_id_);
1835 g_signal_handler_unblock(text_buffer_, mark_set_handler_id2_);
1838 OmniboxViewGtk::CharRange OmniboxViewGtk::GetSelection() const {
1839 // You can not just use get_selection_bounds here, since the order will be
1840 // ascending, and you don't know where the user's start and end of the
1841 // selection was (if the selection was forwards or backwards). Get the
1842 // actual marks so that we can preserve the selection direction.
1843 GtkTextIter start, insert;
1844 GtkTextMark* mark;
1846 mark = gtk_text_buffer_get_selection_bound(text_buffer_);
1847 gtk_text_buffer_get_iter_at_mark(text_buffer_, &start, mark);
1849 mark = gtk_text_buffer_get_insert(text_buffer_);
1850 gtk_text_buffer_get_iter_at_mark(text_buffer_, &insert, mark);
1852 gint start_offset = gtk_text_iter_get_offset(&start);
1853 gint end_offset = gtk_text_iter_get_offset(&insert);
1855 if (supports_pre_edit_) {
1856 // Nothing should be selected when we are in the middle of composition.
1857 DCHECK(pre_edit_.empty() || start_offset == end_offset);
1858 if (!pre_edit_.empty()) {
1859 start_offset += pre_edit_.size();
1860 end_offset += pre_edit_.size();
1864 return CharRange(start_offset, end_offset);
1867 void OmniboxViewGtk::ItersFromCharRange(const CharRange& range,
1868 GtkTextIter* iter_min,
1869 GtkTextIter* iter_max) {
1870 DCHECK(!IsImeComposing());
1871 gtk_text_buffer_get_iter_at_offset(text_buffer_, iter_min, range.cp_min);
1872 gtk_text_buffer_get_iter_at_offset(text_buffer_, iter_max, range.cp_max);
1875 bool OmniboxViewGtk::IsCaretAtEnd() const {
1876 const CharRange selection = GetSelection();
1877 return selection.cp_min == selection.cp_max &&
1878 selection.cp_min == GetOmniboxTextLength();
1881 void OmniboxViewGtk::SavePrimarySelection(const std::string& selected_text) {
1882 DCHECK(text_view_);
1884 GtkClipboard* clipboard =
1885 gtk_widget_get_clipboard(text_view_, GDK_SELECTION_PRIMARY);
1886 DCHECK(clipboard);
1887 if (!clipboard)
1888 return;
1890 gtk_clipboard_set_text(
1891 clipboard, selected_text.data(), selected_text.size());
1894 void OmniboxViewGtk::SetTextAndSelectedRange(const base::string16& text,
1895 const CharRange& range) {
1896 if (text != GetText()) {
1897 std::string utf8 = base::UTF16ToUTF8(text);
1898 gtk_text_buffer_set_text(text_buffer_, utf8.data(), utf8.length());
1900 SetSelectedRange(range);
1901 AdjustTextJustification();
1904 void OmniboxViewGtk::SetSelectedRange(const CharRange& range) {
1905 GtkTextIter insert, bound;
1906 ItersFromCharRange(range, &bound, &insert);
1907 gtk_text_buffer_select_range(text_buffer_, &insert, &bound);
1909 // This should be set *after* setting the selection range, in case setting the
1910 // selection triggers HandleMarkSet which sets |selection_suggested_| to
1911 // false.
1912 selection_suggested_ = true;
1915 void OmniboxViewGtk::AdjustTextJustification() {
1916 DCHECK(text_view_);
1918 PangoDirection content_dir = GetContentDirection();
1920 // Use keymap direction if content does not have strong direction.
1921 // It matches the behavior of GtkTextView.
1922 if (content_dir == PANGO_DIRECTION_NEUTRAL) {
1923 content_dir = gdk_keymap_get_direction(
1924 gdk_keymap_get_for_display(gtk_widget_get_display(text_view_)));
1927 GtkTextDirection widget_dir = gtk_widget_get_direction(text_view_);
1929 if ((widget_dir == GTK_TEXT_DIR_RTL && content_dir == PANGO_DIRECTION_LTR) ||
1930 (widget_dir == GTK_TEXT_DIR_LTR && content_dir == PANGO_DIRECTION_RTL)) {
1931 gtk_text_view_set_justification(GTK_TEXT_VIEW(text_view_),
1932 GTK_JUSTIFY_RIGHT);
1933 } else {
1934 gtk_text_view_set_justification(GTK_TEXT_VIEW(text_view_),
1935 GTK_JUSTIFY_LEFT);
1939 PangoDirection OmniboxViewGtk::GetContentDirection() {
1940 GtkTextIter iter;
1941 gtk_text_buffer_get_start_iter(text_buffer_, &iter);
1943 PangoDirection dir = PANGO_DIRECTION_NEUTRAL;
1944 do {
1945 dir = pango_unichar_direction(gtk_text_iter_get_char(&iter));
1946 if (dir != PANGO_DIRECTION_NEUTRAL)
1947 break;
1948 } while (gtk_text_iter_forward_char(&iter));
1950 return dir;
1953 void OmniboxViewGtk::HandleWidgetDirectionChanged(
1954 GtkWidget* sender,
1955 GtkTextDirection previous_direction) {
1956 AdjustTextJustification();
1959 void OmniboxViewGtk::HandleDeleteFromCursor(GtkWidget* sender,
1960 GtkDeleteType type,
1961 gint count) {
1962 // If the selected text was suggested for autocompletion, then erase those
1963 // first and then let the default handler take over.
1964 if (selection_suggested_) {
1965 gtk_text_buffer_delete_selection(text_buffer_, true, true);
1966 selection_suggested_ = false;
1970 void OmniboxViewGtk::HandleKeymapDirectionChanged(GdkKeymap* sender) {
1971 AdjustTextJustification();
1974 void OmniboxViewGtk::HandleDeleteRange(GtkTextBuffer* buffer,
1975 GtkTextIter* start,
1976 GtkTextIter* end) {
1977 // Prevent the user from deleting the gray text anchor. We can't simply set
1978 // the gray text anchor readonly by applying a tag with "editable" = FALSE,
1979 // because it'll prevent the insert caret from blinking.
1980 ValidateTextBufferIter(start);
1981 ValidateTextBufferIter(end);
1982 if (!gtk_text_iter_compare(start, end)) {
1983 static guint signal_id =
1984 g_signal_lookup("delete-range", GTK_TYPE_TEXT_BUFFER);
1985 g_signal_stop_emission(buffer, signal_id, 0);
1989 void OmniboxViewGtk::HandleMarkSetAlways(GtkTextBuffer* buffer,
1990 GtkTextIter* location,
1991 GtkTextMark* mark) {
1992 if (mark == gray_text_mark_ || !gray_text_mark_)
1993 return;
1995 GtkTextIter new_iter = *location;
1996 ValidateTextBufferIter(&new_iter);
1998 static guint signal_id = g_signal_lookup("mark-set", GTK_TYPE_TEXT_BUFFER);
2000 // "mark-set" signal is actually emitted after the mark's location is already
2001 // set, so if the location is beyond the gray text anchor, we need to move the
2002 // mark again, which will emit the signal again. In order to prevent other
2003 // signal handlers from being called twice, we need to stop signal emission
2004 // before moving the mark again.
2005 if (gtk_text_iter_compare(&new_iter, location)) {
2006 g_signal_stop_emission(buffer, signal_id, 0);
2007 gtk_text_buffer_move_mark(buffer, mark, &new_iter);
2008 return;
2011 if (mark != gtk_text_buffer_get_insert(text_buffer_) &&
2012 mark != gtk_text_buffer_get_selection_bound(text_buffer_)) {
2013 return;
2016 // See issue http://crbug.com/63860
2017 GtkTextIter insert;
2018 GtkTextIter selection_bound;
2019 gtk_text_buffer_get_iter_at_mark(buffer, &insert,
2020 gtk_text_buffer_get_insert(buffer));
2021 gtk_text_buffer_get_iter_at_mark(buffer, &selection_bound,
2022 gtk_text_buffer_get_selection_bound(buffer));
2024 GtkTextIter end;
2025 gtk_text_buffer_get_iter_at_mark(text_buffer_, &end, gray_text_mark_);
2027 if (gtk_text_iter_compare(&insert, &end) > 0 ||
2028 gtk_text_iter_compare(&selection_bound, &end) > 0) {
2029 g_signal_stop_emission(buffer, signal_id, 0);
2033 // static
2034 void OmniboxViewGtk::ClipboardGetSelectionThunk(
2035 GtkClipboard* clipboard,
2036 GtkSelectionData* selection_data,
2037 guint info,
2038 gpointer object) {
2039 OmniboxViewGtk* omnibox_view =
2040 reinterpret_cast<OmniboxViewGtk*>(
2041 g_object_get_data(G_OBJECT(object), kOmniboxViewGtkKey));
2042 omnibox_view->ClipboardGetSelection(clipboard, selection_data, info);
2045 void OmniboxViewGtk::ClipboardGetSelection(GtkClipboard* clipboard,
2046 GtkSelectionData* selection_data,
2047 guint info) {
2048 gtk_selection_data_set_text(selection_data, primary_selection_text_.c_str(),
2049 primary_selection_text_.size());
2052 std::string OmniboxViewGtk::GetSelectedText() const {
2053 GtkTextIter start, end;
2054 std::string result;
2055 if (gtk_text_buffer_get_selection_bounds(text_buffer_, &start, &end)) {
2056 gchar* text = gtk_text_iter_get_text(&start, &end);
2057 size_t text_len = strlen(text);
2058 if (text_len)
2059 result = std::string(text, text_len);
2060 g_free(text);
2062 return result;
2065 void OmniboxViewGtk::UpdatePrimarySelectionIfValidURL() {
2066 base::string16 text = base::UTF8ToUTF16(GetSelectedText());
2068 if (text.empty())
2069 return;
2071 // Use AdjustTextForCopy to make sure we prefix the text with 'http://'.
2072 CharRange selection = GetSelection();
2073 GURL url;
2074 bool write_url;
2075 model()->AdjustTextForCopy(selection.selection_min(), IsSelectAll(), &text,
2076 &url, &write_url);
2077 if (write_url) {
2078 selected_text_ = base::UTF16ToUTF8(text);
2079 OwnPrimarySelection(selected_text_);
2083 void OmniboxViewGtk::HandlePreEditChanged(GtkWidget* sender,
2084 const gchar* pre_edit) {
2085 // GtkTextView won't fire "begin-user-action" and "end-user-action" signals
2086 // when changing the pre-edit string, so we need to call
2087 // OnBeforePossibleChange() and OnAfterPossibleChange() by ourselves.
2088 OnBeforePossibleChange();
2089 if (pre_edit && *pre_edit) {
2090 // GtkTextView will only delete the selection range when committing the
2091 // pre-edit string, which will cause very strange behavior, so we need to
2092 // delete the selection range here explicitly. See http://crbug.com/18808.
2093 if (pre_edit_.empty())
2094 gtk_text_buffer_delete_selection(text_buffer_, false, true);
2095 pre_edit_ = base::UTF8ToUTF16(pre_edit);
2096 } else {
2097 pre_edit_.clear();
2099 OnAfterPossibleChange();
2102 void OmniboxViewGtk::HandleWindowSetFocus(GtkWindow* sender,
2103 GtkWidget* focus) {
2104 // This is actually a guess. If the focused widget changes in "focus-out"
2105 // event handler, then the window will respect that and won't focus
2106 // |focus|. I doubt that is likely to happen however.
2107 going_to_focus_ = focus;
2110 void OmniboxViewGtk::HandleUndoRedo(GtkWidget* sender) {
2111 OnBeforePossibleChange();
2114 void OmniboxViewGtk::HandleUndoRedoAfter(GtkWidget* sender) {
2115 OnAfterPossibleChange();
2118 void OmniboxViewGtk::GetTextBufferBounds(GtkTextIter* start,
2119 GtkTextIter* end) const {
2120 gtk_text_buffer_get_start_iter(text_buffer_, start);
2121 gtk_text_buffer_get_iter_at_mark(text_buffer_, end, gray_text_mark_);
2124 void OmniboxViewGtk::ValidateTextBufferIter(GtkTextIter* iter) const {
2125 if (!gray_text_mark_)
2126 return;
2128 GtkTextIter end;
2129 gtk_text_buffer_get_iter_at_mark(text_buffer_, &end, gray_text_mark_);
2130 if (gtk_text_iter_compare(iter, &end) > 0)
2131 *iter = end;
2134 void OmniboxViewGtk::AdjustVerticalAlignmentOfGrayTextView() {
2135 // By default, GtkTextView layouts an anchored child widget just above the
2136 // baseline, so we need to move the |gray_text_view_| down to make sure it
2137 // has the same baseline as the |text_view_|.
2138 PangoLayout* layout = gtk_label_get_layout(GTK_LABEL(gray_text_view_));
2139 int height;
2140 pango_layout_get_size(layout, NULL, &height);
2141 int baseline = pango_layout_get_baseline(layout);
2142 g_object_set(gray_text_anchor_tag_, "rise", baseline - height, NULL);