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