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>
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"
55 using content::WebContents
;
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.
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;
104 style_was_set
= true;
107 "style \"chrome-location-bar-entry\" {"
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"
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
,
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
),
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
));
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
));
149 std::string menu_item_label
=
150 gtk_menu_item_get_label(GTK_MENU_ITEM(item
->data
));
151 if (menu_item_label
== label
)
162 OmniboxViewGtk::OmniboxViewGtk(OmniboxEditController
* controller
,
165 CommandUpdater
* command_updater
,
166 bool popup_window_mode
,
167 GtkWidget
* location_bar
)
168 : OmniboxView(profile
, controller
, command_updater
),
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
);
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.
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() {
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
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(>k_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_
),
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
,
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
);
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();
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
);
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() {
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
) {
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();
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
);
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();
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
));
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_
);
488 void OmniboxViewGtk::SetWindowTextAndCaretPos(const base::string16
& text
,
491 bool notify_text_changed
) {
492 CharRange
range(static_cast<int>(caret_pos
), static_cast<int>(caret_pos
));
493 SetTextAndSelectedRange(text
, range
);
498 if (notify_text_changed
)
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("?"));
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())
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() ||
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
)
572 bool OmniboxViewGtk::OnInlineAutocompleteTextMaybeChanged(
573 const base::string16
& display_text
,
574 size_t user_text_length
) {
575 if (display_text
== GetText())
578 StartUpdatingHighlightedText();
579 CharRange
range(display_text
.size(), user_text_length
);
580 SetTextAndSelectedRange(display_text
, range
);
581 FinishUpdatingHighlightedText();
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
599 void OmniboxViewGtk::OnBeforePossibleChange() {
600 // Record this paste, so we can do different behavior.
601 if (paste_clipboard_requested_
) {
602 paste_clipboard_requested_
= false;
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_
)
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;
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();
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_
));
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_
) {
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
));
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_
);
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.
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
);
786 void OmniboxViewGtk::SetBaseColor() {
789 bool use_gtk
= theme_service_
->UsingNativeTheme();
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
);
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
);
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() {
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
]);
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
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
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);
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.
1003 static guint signal_id
=
1004 g_signal_lookup("key-press-event", GTK_TYPE_WIDGET
);
1005 g_signal_stop_emission(widget
, signal_id
, 0);
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
)
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;
1062 gboolean
OmniboxViewGtk::HandleViewButtonRelease(GtkWidget
* sender
,
1063 GdkEventButton
* event
) {
1064 if (event
->button
!= 1)
1067 bool button_1_was_pressed
= button_1_pressed_
;
1068 button_1_pressed_
= false;
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
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
) {
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.
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
) {
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
);
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(
1133 GtkMovementStep step
,
1135 gboolean extend_selection
) {
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
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
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();
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
);
1173 } else if (step
== GTK_MOVEMENT_DISPLAY_LINES
) { // Arrow up and down.
1174 model()->OnUpOrDownKeyPressed(count
);
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
,
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.
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)) :
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(
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
) {
1286 void OmniboxViewGtk::HandleMarkSet(GtkTextBuffer
* buffer
,
1287 GtkTextIter
* location
,
1288 GtkTextMark
* mark
) {
1289 if (!text_buffer_
|| buffer
!= text_buffer_
)
1292 if (mark
!= gtk_text_buffer_get_insert(text_buffer_
) &&
1293 mark
!= gtk_text_buffer_get_selection_bound(text_buffer_
)) {
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_
)
1329 // We should only update primary selection when the user changes the selection
1331 if (mark
!= gtk_text_buffer_get_insert(text_buffer_
) &&
1332 mark
!= gtk_text_buffer_get_selection_bound(text_buffer_
)) {
1336 UpdatePrimarySelectionIfValidURL();
1339 // Just use the default behavior for DnD, except if the drop can be a PasteAndGo
1341 void OmniboxViewGtk::HandleDragDataReceived(GtkWidget
* sender
,
1342 GdkDragContext
* context
,
1345 GtkSelectionData
* selection_data
,
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_
))
1360 guchar
* text
= gtk_selection_data_get_text(selection_data
);
1364 base::string16 possible_url
=
1365 base::UTF8ToUTF16(reinterpret_cast<char*>(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
,
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);
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
);
1401 void OmniboxViewGtk::HandleDragBegin(GtkWidget
* widget
,
1402 GdkDragContext
* context
) {
1403 base::string16 text
= base::UTF8ToUTF16(GetSelectedText());
1408 // Use AdjustTextForCopy to make sure we prefix the text with 'http://'.
1409 CharRange selection
= GetSelection();
1412 model()->AdjustTextForCopy(selection
.selection_min(), IsSelectAll(), &text
,
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
,
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.
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.
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.
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_
)
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());
1534 model()->OnUpOrDownKeyPressed(shift_was_pressed_
? -1 : 1);
1539 if (supports_pre_edit_
&& !handled
&& !pre_edit_
.empty())
1542 if (!handled
&& gtk_widget_get_visible(gray_text_view_
))
1543 handled
= model()->CommitSuggestedText();
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
) {
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_
))
1569 GtkClipboard
* clipboard
= gtk_clipboard_get(GDK_SELECTION_PRIMARY
);
1572 CharRange selection
= GetSelection();
1574 base::string16
text(base::UTF8ToUTF16(GetSelectedText()));
1576 model()->AdjustTextForCopy(selection
.selection_min(), IsSelectAll(), &text
,
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.
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 {
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();
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
,
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_
,
1677 } else if (security_level_
== ToolbarModel::SECURITY_WARNING
) {
1678 gtk_text_buffer_apply_tag(text_buffer_
, faded_text_tag_
, &start
, &end
);
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
);
1696 gfx::Font
OmniboxViewGtk::GetFont() {
1697 if (!theme_service_
->UsingNativeTheme()) {
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
);
1710 (rc_style
&& rc_style
->font_desc
) ?
1711 rc_style
->font_desc
: gtk_style
->font_desc
);
1713 g_object_unref(g_object_ref_sink(widget
));
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);
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
),
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
,
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
,
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
)
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
))
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());
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
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());
1799 void OmniboxViewGtk::SelectAllInternal(bool reversed
,
1800 bool update_primary_selection
) {
1801 GtkTextIter start
, end
;
1803 GetTextBufferBounds(&end
, &start
);
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
);
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
);
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
;
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
) {
1884 GtkClipboard
* clipboard
=
1885 gtk_widget_get_clipboard(text_view_
, GDK_SELECTION_PRIMARY
);
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
1912 selection_suggested_
= true;
1915 void OmniboxViewGtk::AdjustTextJustification() {
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_
),
1934 gtk_text_view_set_justification(GTK_TEXT_VIEW(text_view_
),
1939 PangoDirection
OmniboxViewGtk::GetContentDirection() {
1941 gtk_text_buffer_get_start_iter(text_buffer_
, &iter
);
1943 PangoDirection dir
= PANGO_DIRECTION_NEUTRAL
;
1945 dir
= pango_unichar_direction(gtk_text_iter_get_char(&iter
));
1946 if (dir
!= PANGO_DIRECTION_NEUTRAL
)
1948 } while (gtk_text_iter_forward_char(&iter
));
1953 void OmniboxViewGtk::HandleWidgetDirectionChanged(
1955 GtkTextDirection previous_direction
) {
1956 AdjustTextJustification();
1959 void OmniboxViewGtk::HandleDeleteFromCursor(GtkWidget
* sender
,
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
,
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_
)
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
);
2011 if (mark
!= gtk_text_buffer_get_insert(text_buffer_
) &&
2012 mark
!= gtk_text_buffer_get_selection_bound(text_buffer_
)) {
2016 // See issue http://crbug.com/63860
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
));
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);
2034 void OmniboxViewGtk::ClipboardGetSelectionThunk(
2035 GtkClipboard
* clipboard
,
2036 GtkSelectionData
* selection_data
,
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
,
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
;
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
);
2059 result
= std::string(text
, text_len
);
2065 void OmniboxViewGtk::UpdatePrimarySelectionIfValidURL() {
2066 base::string16 text
= base::UTF8ToUTF16(GetSelectedText());
2071 // Use AdjustTextForCopy to make sure we prefix the text with 'http://'.
2072 CharRange selection
= GetSelection();
2075 model()->AdjustTextForCopy(selection
.selection_min(), IsSelectAll(), &text
,
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
);
2099 OnAfterPossibleChange();
2102 void OmniboxViewGtk::HandleWindowSetFocus(GtkWindow
* sender
,
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_
)
2129 gtk_text_buffer_get_iter_at_mark(text_buffer_
, &end
, gray_text_mark_
);
2130 if (gtk_text_iter_compare(iter
, &end
) > 0)
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_
));
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
);