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/chrome_notification_types.h" 21#include "chrome/browser/command_updater.h" 22#include "chrome/browser/defaults.h" 23#include "chrome/browser/platform_util.h" 24#include "chrome/browser/search/search.h" 25#include "chrome/browser/ui/browser.h" 26#include "chrome/browser/ui/gtk/gtk_theme_service.h" 27#include "chrome/browser/ui/gtk/gtk_util.h" 28#include "chrome/browser/ui/gtk/location_bar_view_gtk.h" 29#include "chrome/browser/ui/gtk/omnibox/omnibox_popup_view_gtk.h" 30#include "chrome/browser/ui/gtk/view_id_util.h" 31#include "chrome/browser/ui/omnibox/omnibox_edit_controller.h" 32#include "chrome/browser/ui/omnibox/omnibox_edit_model.h" 33#include "chrome/browser/ui/omnibox/omnibox_popup_model.h" 34#include "chrome/browser/ui/tabs/tab_strip_model.h" 35#include "chrome/browser/ui/toolbar/toolbar_model.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 "grit/generated_resources.h" 40#include "net/base/escape.h" 41#include "third_party/undoview/undo_view.h" 42#include "ui/base/accelerators/menu_label_accelerator_util_linux.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/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/skia_utils_gtk.h" 52#include "url/gurl.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 gray_text_anchor_tag_(NULL), 195 gray_text_view_(NULL), 196 gray_text_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 gray suggestion text view. 366 // GtkLabel is used instead of GtkTextView to get transparent background. 367 gray_text_view_ = gtk_label_new(NULL); 368 gtk_widget_set_no_show_all(gray_text_view_, TRUE); 369 gtk_label_set_selectable(GTK_LABEL(gray_text_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 gray text 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* gray_text_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 gray_text_view_, 384 gray_text_anchor); 385 386 gray_text_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 gray_text_anchor); 391 gtk_text_buffer_apply_tag(text_buffer_, gray_text_anchor_tag_, 392 &anchor_iter, &end_iter); 393 394 GtkTextIter start_iter; 395 gtk_text_buffer_get_start_iter(text_buffer_, &start_iter); 396 gray_text_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 gray text 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 AdjustVerticalAlignmentOfGrayTextView(); 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 // Restore caret visibility if focus is explicitly requested. This is 429 // necessary because if we already have invisible focus, the RequestFocus() 430 // call above will short-circuit, preventing us from reaching 431 // OmniboxEditModel::OnSetFocus(), which handles restoring visibility when the 432 // omnibox regains focus after losing focus. 433 model()->SetCaretVisibility(true); 434} 435 436void OmniboxViewGtk::ApplyCaretVisibility() { 437 gtk_text_view_set_cursor_visible(GTK_TEXT_VIEW(text_view_), 438 model()->is_caret_visible()); 439} 440 441void OmniboxViewGtk::SaveStateToTab(WebContents* tab) { 442 DCHECK(tab); 443 // If any text has been selected, register it as the PRIMARY selection so it 444 // can still be pasted via middle-click after the text view is cleared. 445 if (!selected_text_.empty()) 446 SavePrimarySelection(selected_text_); 447 // NOTE: GetStateForTabSwitch may affect GetSelection, so order is important. 448 OmniboxEditModel::State model_state = model()->GetStateForTabSwitch(); 449 tab->SetUserData( 450 kAutocompleteEditStateKey, 451 new AutocompleteEditState(model_state, ViewState(GetSelection()))); 452} 453 454void OmniboxViewGtk::Update(const WebContents* contents) { 455 // NOTE: We're getting the URL text here from the ToolbarModel. 456 bool visibly_changed_permanent_text = 457 model()->UpdatePermanentText(toolbar_model()->GetText(true)); 458 459 ToolbarModel::SecurityLevel security_level = 460 toolbar_model()->GetSecurityLevel(false); 461 bool changed_security_level = (security_level != security_level_); 462 security_level_ = security_level; 463 464 if (contents) { 465 selected_text_.clear(); 466 RevertAll(); 467 const AutocompleteEditState* state = static_cast<AutocompleteEditState*>( 468 contents->GetUserData(&kAutocompleteEditStateKey)); 469 if (state) { 470 model()->RestoreState(state->model_state); 471 472 // Move the marks for the cursor and the other end of the selection to 473 // the previously-saved offsets (but preserve PRIMARY). 474 StartUpdatingHighlightedText(); 475 SetSelectedRange(state->view_state.selection_range); 476 FinishUpdatingHighlightedText(); 477 } 478 } else if (visibly_changed_permanent_text) { 479 RevertAll(); 480 // TODO(deanm): There should be code to restore select all here. 481 } else if (changed_security_level) { 482 EmphasizeURLComponents(); 483 } 484} 485 486string16 OmniboxViewGtk::GetText() const { 487 GtkTextIter start, end; 488 GetTextBufferBounds(&start, &end); 489 gchar* utf8 = gtk_text_buffer_get_text(text_buffer_, &start, &end, false); 490 string16 out(UTF8ToUTF16(utf8)); 491 g_free(utf8); 492 493 if (supports_pre_edit_) { 494 // We need to treat the text currently being composed by the input method 495 // as part of the text content, so that omnibox can work correctly in the 496 // middle of composition. 497 if (pre_edit_.size()) { 498 GtkTextMark* mark = gtk_text_buffer_get_insert(text_buffer_); 499 gtk_text_buffer_get_iter_at_mark(text_buffer_, &start, mark); 500 out.insert(gtk_text_iter_get_offset(&start), pre_edit_); 501 } 502 } 503 return out; 504} 505 506void OmniboxViewGtk::SetWindowTextAndCaretPos(const string16& text, 507 size_t caret_pos, 508 bool update_popup, 509 bool notify_text_changed) { 510 CharRange range(static_cast<int>(caret_pos), static_cast<int>(caret_pos)); 511 SetTextAndSelectedRange(text, range); 512 513 if (update_popup) 514 UpdatePopup(); 515 516 if (notify_text_changed) 517 TextChanged(); 518} 519 520void OmniboxViewGtk::SetForcedQuery() { 521 const string16 current_text(GetText()); 522 const size_t start = current_text.find_first_not_of(kWhitespaceUTF16); 523 if (start == string16::npos || (current_text[start] != '?')) { 524 SetUserText(ASCIIToUTF16("?")); 525 } else { 526 StartUpdatingHighlightedText(); 527 SetSelectedRange(CharRange(current_text.size(), start + 1)); 528 FinishUpdatingHighlightedText(); 529 } 530} 531 532bool OmniboxViewGtk::IsSelectAll() const { 533 GtkTextIter sel_start, sel_end; 534 gtk_text_buffer_get_selection_bounds(text_buffer_, &sel_start, &sel_end); 535 536 GtkTextIter start, end; 537 GetTextBufferBounds(&start, &end); 538 539 // Returns true if the |text_buffer_| is empty. 540 return gtk_text_iter_equal(&start, &sel_start) && 541 gtk_text_iter_equal(&end, &sel_end); 542} 543 544bool OmniboxViewGtk::DeleteAtEndPressed() { 545 return delete_at_end_pressed_; 546} 547 548void OmniboxViewGtk::GetSelectionBounds(string16::size_type* start, 549 string16::size_type* end) const { 550 CharRange selection = GetSelection(); 551 *start = static_cast<size_t>(selection.cp_min); 552 *end = static_cast<size_t>(selection.cp_max); 553} 554 555void OmniboxViewGtk::SelectAll(bool reversed) { 556 // SelectAll() is invoked as a side effect of other actions (e.g. switching 557 // tabs or hitting Escape) in autocomplete_edit.cc, so we don't update the 558 // PRIMARY selection here. 559 SelectAllInternal(reversed, false); 560} 561 562void OmniboxViewGtk::UpdatePopup() { 563 model()->SetInputInProgress(true); 564 if (!update_popup_without_focus_ && !model()->has_focus()) 565 return; 566 567 // Don't inline autocomplete when the caret/selection isn't at the end of 568 // the text, or in the middle of composition. 569 CharRange sel = GetSelection(); 570 bool no_inline_autocomplete = 571 std::max(sel.cp_max, sel.cp_min) < GetOmniboxTextLength() || 572 IsImeComposing(); 573 model()->StartAutocomplete(sel.cp_min != sel.cp_max, no_inline_autocomplete); 574} 575 576void OmniboxViewGtk::OnTemporaryTextMaybeChanged( 577 const string16& display_text, 578 bool save_original_selection, 579 bool notify_text_changed) { 580 if (save_original_selection) 581 saved_temporary_selection_ = GetSelection(); 582 583 StartUpdatingHighlightedText(); 584 SetWindowTextAndCaretPos(display_text, display_text.length(), false, false); 585 FinishUpdatingHighlightedText(); 586 if (notify_text_changed) 587 TextChanged(); 588} 589 590bool OmniboxViewGtk::OnInlineAutocompleteTextMaybeChanged( 591 const string16& display_text, 592 size_t user_text_length) { 593 if (display_text == GetText()) 594 return false; 595 596 StartUpdatingHighlightedText(); 597 CharRange range(display_text.size(), user_text_length); 598 SetTextAndSelectedRange(display_text, range); 599 FinishUpdatingHighlightedText(); 600 TextChanged(); 601 return true; 602} 603 604void OmniboxViewGtk::OnRevertTemporaryText() { 605 StartUpdatingHighlightedText(); 606 SetSelectedRange(saved_temporary_selection_); 607 FinishUpdatingHighlightedText(); 608 // We got here because the user hit the Escape key. We explicitly don't call 609 // TextChanged(), since OmniboxPopupModel::ResetToDefaultMatch() has already 610 // been called by now, and it would've called TextChanged() if it was 611 // 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::SetGrayTextAutocompletion(const string16& suggestion) { 718 std::string suggestion_utf8 = UTF16ToUTF8(suggestion); 719 720 gtk_label_set_text(GTK_LABEL(gray_text_view_), suggestion_utf8.c_str()); 721 722 if (suggestion.empty()) { 723 gtk_widget_hide(gray_text_view_); 724 return; 725 } 726 727 gtk_widget_show(gray_text_view_); 728 AdjustVerticalAlignmentOfGrayTextView(); 729 UpdateGrayTextViewColors(); 730} 731 732string16 OmniboxViewGtk::GetGrayTextAutocompletion() const { 733 const gchar* suggestion = gtk_label_get_text(GTK_LABEL(gray_text_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 gray suggestion text 758 // 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(gray_text_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(gray_text_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 AdjustVerticalAlignmentOfGrayTextView(); 854 UpdateGrayTextViewColors(); 855} 856 857void OmniboxViewGtk::UpdateGrayTextViewColors() { 858 GdkColor faded_text; 859 if (theme_service_->UsingNativeTheme()) { 860 GtkStyle* style = gtk_rc_get_style(gray_text_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(gray_text_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 // Restore caret visibility whenever the user clicks in the omnibox in a way 1049 // that would give it focus. We must handle this case separately here because 1050 // if the omnibox currently has invisible focus, the mouse event won't trigger 1051 // either SetFocus() or OmniboxEditModel::OnSetFocus(). 1052 if (event->button == 1 || event->button == 2) 1053 model()->SetCaretVisibility(true); 1054 1055 if (event->button == 1) { 1056 button_1_pressed_ = true; 1057 1058 // Button press event may change the selection, we need to record the change 1059 // and report it to model() later when button is released. 1060 OnBeforePossibleChange(); 1061 } else if (event->button == 2) { 1062 // GtkTextView pastes PRIMARY selection with middle click. 1063 // We can't call model()->on_paste_replacing_all() here, because the actual 1064 // paste clipboard action may not be performed if the clipboard is empty. 1065 paste_clipboard_requested_ = true; 1066 } 1067 return FALSE; 1068} 1069 1070gboolean OmniboxViewGtk::HandleViewButtonRelease(GtkWidget* sender, 1071 GdkEventButton* event) { 1072 if (event->button != 1) 1073 return FALSE; 1074 1075 bool button_1_was_pressed = button_1_pressed_; 1076 button_1_pressed_ = false; 1077 1078 DCHECK(text_view_); 1079 1080 // Call the GtkTextView default handler, ignoring the fact that it will 1081 // likely have told us to stop propagating. We want to handle selection. 1082 GtkWidgetClass* klass = GTK_WIDGET_GET_CLASS(text_view_); 1083 klass->button_release_event(text_view_, event); 1084 1085 // Inform model() about possible text selection change. We may get a button 1086 // release with no press (e.g. if the user clicks in the omnibox to dismiss a 1087 // bubble). 1088 if (button_1_was_pressed) 1089 OnAfterPossibleChange(); 1090 1091 return TRUE; // Don't continue, we called the default handler already. 1092} 1093 1094gboolean OmniboxViewGtk::HandleViewFocusIn(GtkWidget* sender, 1095 GdkEventFocus* event) { 1096 DCHECK(text_view_); 1097 update_popup_without_focus_ = false; 1098 1099 GdkModifierType modifiers; 1100 GdkWindow* gdk_window = gtk_widget_get_window(text_view_); 1101 gdk_window_get_pointer(gdk_window, NULL, NULL, &modifiers); 1102 model()->OnSetFocus((modifiers & GDK_CONTROL_MASK) != 0); 1103 controller()->OnSetFocus(); 1104 // TODO(deanm): Some keyword hit business, etc here. 1105 1106 g_signal_connect( 1107 gdk_keymap_get_for_display(gtk_widget_get_display(text_view_)), 1108 "direction-changed", 1109 G_CALLBACK(&HandleKeymapDirectionChangedThunk), this); 1110 1111 AdjustTextJustification(); 1112 1113 return FALSE; // Continue propagation. 1114} 1115 1116gboolean OmniboxViewGtk::HandleViewFocusOut(GtkWidget* sender, 1117 GdkEventFocus* event) { 1118 DCHECK(text_view_); 1119 GtkWidget* view_getting_focus = NULL; 1120 GtkWindow* toplevel = platform_util::GetTopLevel(sender); 1121 if (gtk_window_is_active(toplevel)) 1122 view_getting_focus = going_to_focus_; 1123 1124 // This must be invoked before ClosePopup. 1125 model()->OnWillKillFocus(view_getting_focus); 1126 1127 // Close the popup. 1128 CloseOmniboxPopup(); 1129 // Tell the model to reset itself. 1130 model()->OnKillFocus(); 1131 controller()->OnKillFocus(); 1132 1133 g_signal_handlers_disconnect_by_func( 1134 gdk_keymap_get_for_display(gtk_widget_get_display(text_view_)), 1135 reinterpret_cast<gpointer>(&HandleKeymapDirectionChangedThunk), this); 1136 1137 return FALSE; // Pass the event on to the GtkTextView. 1138} 1139 1140void OmniboxViewGtk::HandleViewMoveCursor( 1141 GtkWidget* sender, 1142 GtkMovementStep step, 1143 gint count, 1144 gboolean extend_selection) { 1145 DCHECK(text_view_); 1146 GtkTextIter sel_start, sel_end; 1147 gboolean has_selection = 1148 gtk_text_buffer_get_selection_bounds(text_buffer_, &sel_start, &sel_end); 1149 bool handled = false; 1150 1151 if (step == GTK_MOVEMENT_VISUAL_POSITIONS && !extend_selection && 1152 (count == 1 || count == -1)) { 1153 // We need to take the content direction into account when handling cursor 1154 // movement, because the behavior of Left and Right key will be inverted if 1155 // the direction is RTL. Although we should check the direction around the 1156 // input caret, it's much simpler and good enough to check whole content's 1157 // direction. 1158 PangoDirection content_dir = GetContentDirection(); 1159 gint count_towards_end = content_dir == PANGO_DIRECTION_RTL ? -1 : 1; 1160 1161 // We want the GtkEntry behavior when you move the cursor while you have a 1162 // selection. GtkTextView just drops the selection and moves the cursor, 1163 // but instead we want to move the cursor to the appropiate end of the 1164 // selection. 1165 if (has_selection) { 1166 // We have a selection and start / end are in ascending order. 1167 // Cursor placement will remove the selection, so we need inform 1168 // model() about this change by 1169 // calling On{Before|After}PossibleChange() methods. 1170 OnBeforePossibleChange(); 1171 gtk_text_buffer_place_cursor( 1172 text_buffer_, count == count_towards_end ? &sel_end : &sel_start); 1173 OnAfterPossibleChange(); 1174 handled = true; 1175 } else if (count == count_towards_end && !IsCaretAtEnd()) { 1176 handled = model()->CommitSuggestedText(); 1177 } 1178 } else if (step == GTK_MOVEMENT_PAGES) { // Page up and down. 1179 // Multiply by count for the direction (if we move too much that's ok). 1180 model()->OnUpOrDownKeyPressed(model()->result().size() * count); 1181 handled = true; 1182 } else if (step == GTK_MOVEMENT_DISPLAY_LINES) { // Arrow up and down. 1183 model()->OnUpOrDownKeyPressed(count); 1184 handled = true; 1185 } 1186 1187 if (!handled) { 1188 // Cursor movement may change the selection, we need to record the change 1189 // and report it to model(). 1190 if (has_selection || extend_selection) 1191 OnBeforePossibleChange(); 1192 1193 // Propagate into GtkTextView 1194 GtkTextViewClass* klass = GTK_TEXT_VIEW_GET_CLASS(text_view_); 1195 klass->move_cursor(GTK_TEXT_VIEW(text_view_), step, count, 1196 extend_selection); 1197 1198 if (has_selection || extend_selection) 1199 OnAfterPossibleChange(); 1200 } 1201 1202 // move-cursor doesn't use a signal accumulator on the return value (it 1203 // just ignores then), so we have to stop the propagation. 1204 static guint signal_id = g_signal_lookup("move-cursor", GTK_TYPE_TEXT_VIEW); 1205 g_signal_stop_emission(text_view_, signal_id, 0); 1206} 1207 1208void OmniboxViewGtk::HandleViewSizeRequest(GtkWidget* sender, 1209 GtkRequisition* req) { 1210 // Don't force a minimum width, but use the font-relative height. This is a 1211 // run-first handler, so the default handler was already called. 1212 req->width = 1; 1213} 1214 1215void OmniboxViewGtk::HandlePopupMenuDeactivate(GtkWidget* sender) { 1216 // When the context menu appears, |text_view_|'s focus is lost. After an item 1217 // is activated, the focus comes back to |text_view_|, but only after the 1218 // check in UpdatePopup(). We set this flag to make UpdatePopup() aware that 1219 // it will be receiving focus again. 1220 if (!model()->has_focus()) 1221 update_popup_without_focus_ = true; 1222} 1223 1224void OmniboxViewGtk::HandlePopulatePopup(GtkWidget* sender, GtkMenu* menu) { 1225 GtkWidget* separator = gtk_separator_menu_item_new(); 1226 gtk_menu_shell_append(GTK_MENU_SHELL(menu), separator); 1227 gtk_widget_show(separator); 1228 1229 // Search Engine menu item. 1230 GtkWidget* search_engine_menuitem = gtk_menu_item_new_with_mnemonic( 1231 ui::ConvertAcceleratorsFromWindowsStyle( 1232 l10n_util::GetStringUTF8(IDS_EDIT_SEARCH_ENGINES)).c_str()); 1233 gtk_menu_shell_append(GTK_MENU_SHELL(menu), search_engine_menuitem); 1234 g_signal_connect(search_engine_menuitem, "activate", 1235 G_CALLBACK(HandleEditSearchEnginesThunk), this); 1236 gtk_widget_set_sensitive(search_engine_menuitem, 1237 command_updater()->IsCommandEnabled(IDC_EDIT_SEARCH_ENGINES)); 1238 gtk_widget_show(search_engine_menuitem); 1239 1240 GtkClipboard* x_clipboard = gtk_clipboard_get(GDK_SELECTION_CLIPBOARD); 1241 gchar* text = gtk_clipboard_wait_for_text(x_clipboard); 1242 sanitized_text_for_paste_and_go_ = text ? 1243 StripJavascriptSchemas(CollapseWhitespace(UTF8ToUTF16(text), true)) : 1244 string16(); 1245 g_free(text); 1246 1247 // Copy URL menu item. 1248 if (chrome::IsQueryExtractionEnabled()) { 1249 GtkWidget* copy_url_menuitem = gtk_menu_item_new_with_mnemonic( 1250 ui::ConvertAcceleratorsFromWindowsStyle( 1251 l10n_util::GetStringUTF8(IDS_COPY_URL)).c_str()); 1252 1253 // Detect the Paste and Copy menu items by searching for the ones that use 1254 // the stock labels (i.e. GTK_STOCK_PASTE and GTK_STOCK_COPY). 1255 1256 // If we don't find the stock Copy menu item, the Copy URL item will be 1257 // appended at the end of the popup menu. 1258 gtk_menu_shell_insert(GTK_MENU_SHELL(menu), copy_url_menuitem, 1259 GetPopupMenuIndexForStockLabel(GTK_STOCK_COPY, menu)); 1260 g_signal_connect(copy_url_menuitem, "activate", 1261 G_CALLBACK(HandleCopyURLClipboardThunk), this); 1262 gtk_widget_set_sensitive( 1263 copy_url_menuitem, 1264 toolbar_model()->WouldReplaceSearchURLWithSearchTerms(false) && 1265 !model()->user_input_in_progress()); 1266 gtk_widget_show(copy_url_menuitem); 1267 } 1268 1269 // Paste and Go menu item. 1270 GtkWidget* paste_go_menuitem = gtk_menu_item_new_with_mnemonic( 1271 ui::ConvertAcceleratorsFromWindowsStyle(l10n_util::GetStringUTF8( 1272 model()->IsPasteAndSearch(sanitized_text_for_paste_and_go_) ? 1273 IDS_PASTE_AND_SEARCH : IDS_PASTE_AND_GO)).c_str()); 1274 1275 // If we don't find the stock Paste menu item, the Paste and Go item will be 1276 // appended at the end of the popup menu. 1277 gtk_menu_shell_insert(GTK_MENU_SHELL(menu), paste_go_menuitem, 1278 GetPopupMenuIndexForStockLabel(GTK_STOCK_PASTE, menu)); 1279 1280 g_signal_connect(paste_go_menuitem, "activate", 1281 G_CALLBACK(HandlePasteAndGoThunk), this); 1282 gtk_widget_set_sensitive(paste_go_menuitem, 1283 model()->CanPasteAndGo(sanitized_text_for_paste_and_go_)); 1284 gtk_widget_show(paste_go_menuitem); 1285 1286 g_signal_connect(menu, "deactivate", 1287 G_CALLBACK(HandlePopupMenuDeactivateThunk), this); 1288} 1289 1290void OmniboxViewGtk::HandleEditSearchEngines(GtkWidget* sender) { 1291 command_updater()->ExecuteCommand(IDC_EDIT_SEARCH_ENGINES); 1292} 1293 1294void OmniboxViewGtk::HandlePasteAndGo(GtkWidget* sender) { 1295 model()->PasteAndGo(sanitized_text_for_paste_and_go_); 1296} 1297 1298void OmniboxViewGtk::HandleMarkSet(GtkTextBuffer* buffer, 1299 GtkTextIter* location, 1300 GtkTextMark* mark) { 1301 if (!text_buffer_ || buffer != text_buffer_) 1302 return; 1303 1304 if (mark != gtk_text_buffer_get_insert(text_buffer_) && 1305 mark != gtk_text_buffer_get_selection_bound(text_buffer_)) { 1306 return; 1307 } 1308 1309 // If we are here, that means the user may be changing the selection 1310 selection_suggested_ = false; 1311 1312 // Get the currently-selected text, if there is any. 1313 std::string new_selected_text = GetSelectedText(); 1314 1315 // If we had some text selected earlier but it's no longer highlighted, we 1316 // might need to save it now... 1317 if (!selected_text_.empty() && new_selected_text.empty()) { 1318 // ... but only if we currently own the selection. We want to manually 1319 // update the selection when the text is unhighlighted because the user 1320 // clicked in a blank area of the text view, but not when it's unhighlighted 1321 // because another client or widget took the selection. (This handler gets 1322 // called before the default handler, so as long as nobody else took the 1323 // selection, the text buffer still owns it even if GTK is about to take it 1324 // away in the default handler.) 1325 GtkClipboard* clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY); 1326 if (gtk_clipboard_get_owner(clipboard) == G_OBJECT(text_buffer_)) 1327 SavePrimarySelection(selected_text_); 1328 } 1329 1330 selected_text_ = new_selected_text; 1331} 1332 1333// Override the primary selection the text buffer has set. This has to happen 1334// after the default handler for the "mark-set" signal. 1335void OmniboxViewGtk::HandleMarkSetAfter(GtkTextBuffer* buffer, 1336 GtkTextIter* location, 1337 GtkTextMark* mark) { 1338 if (!text_buffer_ || buffer != text_buffer_) 1339 return; 1340 1341 // We should only update primary selection when the user changes the selection 1342 // range. 1343 if (mark != gtk_text_buffer_get_insert(text_buffer_) && 1344 mark != gtk_text_buffer_get_selection_bound(text_buffer_)) { 1345 return; 1346 } 1347 1348 UpdatePrimarySelectionIfValidURL(); 1349} 1350 1351// Just use the default behavior for DnD, except if the drop can be a PasteAndGo 1352// then override. 1353void OmniboxViewGtk::HandleDragDataReceived(GtkWidget* sender, 1354 GdkDragContext* context, 1355 gint x, 1356 gint y, 1357 GtkSelectionData* selection_data, 1358 guint target_type, 1359 guint time) { 1360 DCHECK(text_view_); 1361 1362 // Reset |paste_clipboard_requested_| to make sure we won't misinterpret this 1363 // drop action as a paste action. 1364 paste_clipboard_requested_ = false; 1365 1366 // Don't try to PasteAndGo on drops originating from this omnibox. However, do 1367 // allow default behavior for such drags. 1368 if (gdk_drag_context_get_source_window(context) == 1369 gtk_widget_get_window(text_view_)) 1370 return; 1371 1372 guchar* text = gtk_selection_data_get_text(selection_data); 1373 if (!text) 1374 return; 1375 1376 string16 possible_url = UTF8ToUTF16(reinterpret_cast<char*>(text)); 1377 g_free(text); 1378 if (OnPerformDropImpl(possible_url)) { 1379 gtk_drag_finish(context, TRUE, FALSE, time); 1380 1381 static guint signal_id = 1382 g_signal_lookup("drag-data-received", GTK_TYPE_WIDGET); 1383 g_signal_stop_emission(text_view_, signal_id, 0); 1384 } 1385} 1386 1387void OmniboxViewGtk::HandleDragDataGet(GtkWidget* widget, 1388 GdkDragContext* context, 1389 GtkSelectionData* selection_data, 1390 guint target_type, 1391 guint time) { 1392 DCHECK(text_view_); 1393 1394 switch (target_type) { 1395 case GTK_TEXT_BUFFER_TARGET_INFO_TEXT: { 1396 gtk_selection_data_set_text(selection_data, dragged_text_.c_str(), -1); 1397 break; 1398 } 1399 case ui::CHROME_NAMED_URL: { 1400 WebContents* current_tab = 1401 browser_->tab_strip_model()->GetActiveWebContents(); 1402 string16 tab_title = current_tab->GetTitle(); 1403 // Pass an empty string if user has edited the URL. 1404 if (current_tab->GetURL().spec() != dragged_text_) 1405 tab_title = string16(); 1406 ui::WriteURLWithName(selection_data, GURL(dragged_text_), 1407 tab_title, target_type); 1408 break; 1409 } 1410 } 1411} 1412 1413void OmniboxViewGtk::HandleDragBegin(GtkWidget* widget, 1414 GdkDragContext* context) { 1415 string16 text = UTF8ToUTF16(GetSelectedText()); 1416 1417 if (text.empty()) 1418 return; 1419 1420 // Use AdjustTextForCopy to make sure we prefix the text with 'http://'. 1421 CharRange selection = GetSelection(); 1422 GURL url; 1423 bool write_url; 1424 model()->AdjustTextForCopy(selection.selection_min(), IsSelectAll(), &text, 1425 &url, &write_url); 1426 if (write_url) { 1427 selected_text_ = UTF16ToUTF8(text); 1428 GtkTargetList* copy_targets = 1429 gtk_text_buffer_get_copy_target_list(text_buffer_); 1430 gtk_target_list_add(copy_targets, 1431 ui::GetAtomForTarget(ui::CHROME_NAMED_URL), 1432 GTK_TARGET_SAME_APP, ui::CHROME_NAMED_URL); 1433 } 1434 dragged_text_ = selected_text_; 1435} 1436 1437void OmniboxViewGtk::HandleDragEnd(GtkWidget* widget, 1438 GdkDragContext* context) { 1439 GdkAtom atom = ui::GetAtomForTarget(ui::CHROME_NAMED_URL); 1440 GtkTargetList* copy_targets = 1441 gtk_text_buffer_get_copy_target_list(text_buffer_); 1442 gtk_target_list_remove(copy_targets, atom); 1443 dragged_text_.clear(); 1444} 1445 1446void OmniboxViewGtk::HandleInsertText(GtkTextBuffer* buffer, 1447 GtkTextIter* location, 1448 const gchar* text, 1449 gint len) { 1450 string16 filtered_text; 1451 filtered_text.reserve(len); 1452 1453 // Filter out new line and tab characters. 1454 // |text| is guaranteed to be a valid UTF-8 string, so we don't need to 1455 // validate it here. 1456 // 1457 // If there was only a single character, then it might be generated by a key 1458 // event. In this case, we save the single character to help our 1459 // "key-press-event" signal handler distinguish if an Enter key event is 1460 // handled by IME or not. 1461 if (len == 1 && (text[0] == '\n' || text[0] == '\r')) 1462 enter_was_inserted_ = true; 1463 1464 for (const gchar* p = text; *p && (p - text) < len; 1465 p = g_utf8_next_char(p)) { 1466 gunichar c = g_utf8_get_char(p); 1467 1468 // 0x200B is Zero Width Space, which is inserted just before the gray text 1469 // anchor for working around the GtkTextView's misalignment bug. 1470 // This character might be captured and inserted into the content by undo 1471 // manager, so we need to filter it out here. 1472 if (c != 0x200B) 1473 base::WriteUnicodeCharacter(c, &filtered_text); 1474 } 1475 1476 if (model()->is_pasting()) { 1477 // If the user is pasting all-whitespace, paste a single space 1478 // rather than nothing, since pasting nothing feels broken. 1479 filtered_text = CollapseWhitespace(filtered_text, true); 1480 filtered_text = filtered_text.empty() ? ASCIIToUTF16(" ") : 1481 StripJavascriptSchemas(filtered_text); 1482 } 1483 1484 if (!filtered_text.empty()) { 1485 // Avoid inserting the text after the gray text anchor. 1486 ValidateTextBufferIter(location); 1487 1488 // Call the default handler to insert filtered text. 1489 GtkTextBufferClass* klass = GTK_TEXT_BUFFER_GET_CLASS(buffer); 1490 std::string utf8_text = UTF16ToUTF8(filtered_text); 1491 klass->insert_text(buffer, location, utf8_text.data(), 1492 static_cast<gint>(utf8_text.length())); 1493 } 1494 1495 // Stop propagating the signal emission to prevent the default handler from 1496 // being called again. 1497 static guint signal_id = g_signal_lookup("insert-text", GTK_TYPE_TEXT_BUFFER); 1498 g_signal_stop_emission(buffer, signal_id, 0); 1499} 1500 1501void OmniboxViewGtk::HandleBackSpace(GtkWidget* sender) { 1502 // Checks if it's currently in keyword search mode. 1503 if (model()->is_keyword_hint() || model()->keyword().empty()) 1504 return; // Propgate into GtkTextView. 1505 1506 DCHECK(text_view_); 1507 1508 GtkTextIter sel_start, sel_end; 1509 // Checks if there is some text selected. 1510 if (gtk_text_buffer_get_selection_bounds(text_buffer_, &sel_start, &sel_end)) 1511 return; // Propgate into GtkTextView. 1512 1513 GtkTextIter start; 1514 gtk_text_buffer_get_start_iter(text_buffer_, &start); 1515 1516 if (!gtk_text_iter_equal(&start, &sel_start)) 1517 return; // Propgate into GtkTextView. 1518 1519 // We're showing a keyword and the user pressed backspace at the beginning 1520 // of the text. Delete the selected keyword. 1521 model()->ClearKeyword(GetText()); 1522 1523 // Stop propagating the signal emission into GtkTextView. 1524 static guint signal_id = g_signal_lookup("backspace", GTK_TYPE_TEXT_VIEW); 1525 g_signal_stop_emission(text_view_, signal_id, 0); 1526} 1527 1528void OmniboxViewGtk::HandleViewMoveFocus(GtkWidget* widget, 1529 GtkDirectionType direction) { 1530 if (!tab_was_pressed_) 1531 return; 1532 1533 // If special behavior is triggered, then stop the signal emission to 1534 // prevent the focus from being moved. 1535 bool handled = false; 1536 1537 // Trigger Tab to search behavior only when Tab key is pressed. 1538 if (model()->is_keyword_hint() && !shift_was_pressed_) { 1539 handled = model()->AcceptKeyword(ENTERED_KEYWORD_MODE_VIA_TAB); 1540 } else if (model()->popup_model()->IsOpen()) { 1541 if (shift_was_pressed_ && 1542 model()->popup_model()->selected_line_state() == 1543 OmniboxPopupModel::KEYWORD) 1544 model()->ClearKeyword(GetText()); 1545 else 1546 model()->OnUpOrDownKeyPressed(shift_was_pressed_ ? -1 : 1); 1547 1548 handled = true; 1549 } 1550 1551 if (supports_pre_edit_ && !handled && !pre_edit_.empty()) 1552 handled = true; 1553 1554 if (!handled && gtk_widget_get_visible(gray_text_view_)) 1555 handled = model()->CommitSuggestedText(); 1556 1557 if (handled) { 1558 static guint signal_id = g_signal_lookup("move-focus", GTK_TYPE_WIDGET); 1559 g_signal_stop_emission(widget, signal_id, 0); 1560 } 1561} 1562 1563void OmniboxViewGtk::HandleCopyClipboard(GtkWidget* sender) { 1564 HandleCopyOrCutClipboard(true); 1565} 1566 1567void OmniboxViewGtk::HandleCopyURLClipboard(GtkWidget* sender) { 1568 DoWriteToClipboard(toolbar_model()->GetURL(), 1569 toolbar_model()->GetText(false)); 1570} 1571 1572void OmniboxViewGtk::HandleCutClipboard(GtkWidget* sender) { 1573 HandleCopyOrCutClipboard(false); 1574} 1575 1576void OmniboxViewGtk::HandleCopyOrCutClipboard(bool copy) { 1577 DCHECK(text_view_); 1578 1579 // On copy or cut, we manually update the PRIMARY selection to contain the 1580 // highlighted text. This matches Firefox -- we highlight the URL but don't 1581 // update PRIMARY on Ctrl-L, so Ctrl-L, Ctrl-C and then middle-click is a 1582 // convenient way to paste the current URL somewhere. 1583 if (!gtk_text_buffer_get_has_selection(text_buffer_)) 1584 return; 1585 1586 GtkClipboard* clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY); 1587 DCHECK(clipboard); 1588 1589 CharRange selection = GetSelection(); 1590 GURL url; 1591 string16 text(UTF8ToUTF16(GetSelectedText())); 1592 bool write_url; 1593 model()->AdjustTextForCopy(selection.selection_min(), IsSelectAll(), &text, 1594 &url, &write_url); 1595 1596 // On other platforms we write |text| to the clipboard regardless of 1597 // |write_url|. We don't need to do that here because we fall through to 1598 // the default signal handlers. 1599 if (write_url) { 1600 DoWriteToClipboard(url, text); 1601 SetSelectedRange(selection); 1602 1603 // Stop propagating the signal. 1604 static guint copy_signal_id = 1605 g_signal_lookup("copy-clipboard", GTK_TYPE_TEXT_VIEW); 1606 static guint cut_signal_id = 1607 g_signal_lookup("cut-clipboard", GTK_TYPE_TEXT_VIEW); 1608 g_signal_stop_emission(text_view_, 1609 copy ? copy_signal_id : cut_signal_id, 1610 0); 1611 1612 if (!copy && gtk_text_view_get_editable(GTK_TEXT_VIEW(text_view_))) 1613 gtk_text_buffer_delete_selection(text_buffer_, true, true); 1614 } 1615 1616 OwnPrimarySelection(UTF16ToUTF8(text)); 1617} 1618 1619int OmniboxViewGtk::GetOmniboxTextLength() const { 1620 GtkTextIter end; 1621 gtk_text_buffer_get_iter_at_mark(text_buffer_, &end, gray_text_mark_); 1622 if (supports_pre_edit_) { 1623 // We need to count the length of the text being composed, because we treat 1624 // it as part of the content in GetText(). 1625 return gtk_text_iter_get_offset(&end) + pre_edit_.size(); 1626 } 1627 return gtk_text_iter_get_offset(&end); 1628} 1629 1630void OmniboxViewGtk::EmphasizeURLComponents() { 1631 if (supports_pre_edit_) { 1632 // We can't change the text style easily, if the pre-edit string (the text 1633 // being composed by the input method) is not empty, which is not treated as 1634 // a part of the text content inside GtkTextView. And it's ok to simply 1635 // return in this case, as this method will be called again when the 1636 // pre-edit string gets committed. 1637 if (pre_edit_.size()) { 1638 strikethrough_ = CharRange(); 1639 return; 1640 } 1641 } 1642 // See whether the contents are a URL with a non-empty host portion, which we 1643 // should emphasize. To check for a URL, rather than using the type returned 1644 // by Parse(), ask the model, which will check the desired page transition for 1645 // this input. This can tell us whether an UNKNOWN input string is going to 1646 // be treated as a search or a navigation, and is the same method the Paste 1647 // And Go system uses. 1648 url_parse::Component scheme, host; 1649 string16 text(GetText()); 1650 AutocompleteInput::ParseForEmphasizeComponents(text, &scheme, &host); 1651 1652 // Set the baseline emphasis. 1653 GtkTextIter start, end; 1654 GetTextBufferBounds(&start, &end); 1655 gtk_text_buffer_remove_all_tags(text_buffer_, &start, &end); 1656 bool grey_out_url = text.substr(scheme.begin, scheme.len) == 1657 UTF8ToUTF16(extensions::kExtensionScheme); 1658 bool grey_base = model()->CurrentTextIsURL() && 1659 (host.is_nonempty() || grey_out_url); 1660 gtk_text_buffer_apply_tag( 1661 text_buffer_, grey_base ? faded_text_tag_ : normal_text_tag_ , &start, 1662 &end); 1663 1664 if (grey_base && !grey_out_url) { 1665 // We've found a host name, give it more emphasis. 1666 gtk_text_buffer_get_iter_at_line_index( 1667 text_buffer_, &start, 0, GetUTF8Offset(text, host.begin)); 1668 gtk_text_buffer_get_iter_at_line_index( 1669 text_buffer_, &end, 0, GetUTF8Offset(text, host.end())); 1670 gtk_text_buffer_apply_tag(text_buffer_, normal_text_tag_, &start, &end); 1671 } 1672 1673 strikethrough_ = CharRange(); 1674 // Emphasize the scheme for security UI display purposes (if necessary). 1675 if (!model()->user_input_in_progress() && model()->CurrentTextIsURL() && 1676 scheme.is_nonempty() && (security_level_ != ToolbarModel::NONE)) { 1677 CharRange scheme_range = CharRange(GetUTF8Offset(text, scheme.begin), 1678 GetUTF8Offset(text, scheme.end())); 1679 ItersFromCharRange(scheme_range, &start, &end); 1680 1681 if (security_level_ == ToolbarModel::SECURITY_ERROR) { 1682 strikethrough_ = scheme_range; 1683 // When we draw the strikethrough, we don't want to include the ':' at the 1684 // end of the scheme. 1685 strikethrough_.cp_max--; 1686 1687 gtk_text_buffer_apply_tag(text_buffer_, security_error_scheme_tag_, 1688 &start, &end); 1689 } else if (security_level_ == ToolbarModel::SECURITY_WARNING) { 1690 gtk_text_buffer_apply_tag(text_buffer_, faded_text_tag_, &start, &end); 1691 } else { 1692 gtk_text_buffer_apply_tag(text_buffer_, secure_scheme_tag_, &start, &end); 1693 } 1694 } 1695} 1696 1697bool OmniboxViewGtk::OnPerformDropImpl(const string16& text) { 1698 string16 sanitized_string(StripJavascriptSchemas( 1699 CollapseWhitespace(text, true))); 1700 if (model()->CanPasteAndGo(sanitized_string)) { 1701 model()->PasteAndGo(sanitized_string); 1702 return true; 1703 } 1704 1705 return false; 1706} 1707 1708gfx::Font OmniboxViewGtk::GetFont() { 1709 if (!theme_service_->UsingNativeTheme()) { 1710 return gfx::Font( 1711 ui::ResourceBundle::GetSharedInstance().GetFont( 1712 ui::ResourceBundle::BaseFont).GetFontName(), 1713 browser_defaults::kOmniboxFontPixelSize); 1714 } 1715 1716 // If we haven't initialized the text view yet, just create a temporary one 1717 // whose style we can grab. 1718 GtkWidget* widget = text_view_ ? text_view_ : gtk_text_view_new(); 1719 GtkStyle* gtk_style = gtk_widget_get_style(widget); 1720 GtkRcStyle* rc_style = gtk_widget_get_modifier_style(widget); 1721 gfx::Font font( 1722 (rc_style && rc_style->font_desc) ? 1723 rc_style->font_desc : gtk_style->font_desc); 1724 if (!text_view_) 1725 g_object_unref(g_object_ref_sink(widget)); 1726 return font; 1727} 1728 1729void OmniboxViewGtk::OwnPrimarySelection(const std::string& text) { 1730 primary_selection_text_ = text; 1731 1732 GtkTargetList* list = gtk_target_list_new(NULL, 0); 1733 gtk_target_list_add_text_targets(list, 0); 1734 gint len; 1735 GtkTargetEntry* entries = gtk_target_table_new_from_list(list, &len); 1736 1737 // When |text_buffer_| is destroyed, it will clear the clipboard, hence 1738 // we needn't worry about calling gtk_clipboard_clear(). 1739 gtk_clipboard_set_with_owner(gtk_clipboard_get(GDK_SELECTION_PRIMARY), 1740 entries, len, 1741 ClipboardGetSelectionThunk, 1742 ClipboardSelectionCleared, 1743 G_OBJECT(text_buffer_)); 1744 1745 gtk_target_list_unref(list); 1746 gtk_target_table_free(entries, len); 1747} 1748 1749void OmniboxViewGtk::HandlePasteClipboard(GtkWidget* sender) { 1750 // We can't call model()->on_paste_replacing_all() here, because the actual 1751 // paste clipboard action may not be performed if the clipboard is empty. 1752 paste_clipboard_requested_ = true; 1753} 1754 1755gfx::Rect OmniboxViewGtk::WindowBoundsFromIters(GtkTextIter* iter1, 1756 GtkTextIter* iter2) { 1757 GdkRectangle start_location, end_location; 1758 GtkTextView* text_view = GTK_TEXT_VIEW(text_view_); 1759 gtk_text_view_get_iter_location(text_view, iter1, &start_location); 1760 gtk_text_view_get_iter_location(text_view, iter2, &end_location); 1761 1762 gint x1, x2, y1, y2; 1763 gtk_text_view_buffer_to_window_coords(text_view, GTK_TEXT_WINDOW_WIDGET, 1764 start_location.x, start_location.y, 1765 &x1, &y1); 1766 gtk_text_view_buffer_to_window_coords(text_view, GTK_TEXT_WINDOW_WIDGET, 1767 end_location.x + end_location.width, 1768 end_location.y + end_location.height, 1769 &x2, &y2); 1770 1771 return gfx::Rect(x1, y1, x2 - x1, y2 - y1); 1772} 1773 1774gboolean OmniboxViewGtk::HandleExposeEvent(GtkWidget* sender, 1775 GdkEventExpose* expose) { 1776 if (strikethrough_.cp_min >= strikethrough_.cp_max) 1777 return FALSE; 1778 DCHECK(text_view_); 1779 1780 gfx::Rect expose_rect(expose->area); 1781 1782 GtkTextIter iter_min, iter_max; 1783 ItersFromCharRange(strikethrough_, &iter_min, &iter_max); 1784 gfx::Rect strikethrough_rect = WindowBoundsFromIters(&iter_min, &iter_max); 1785 1786 if (!expose_rect.Intersects(strikethrough_rect)) 1787 return FALSE; 1788 1789 // Finally, draw. 1790 cairo_t* cr = gdk_cairo_create(GDK_DRAWABLE(expose->window)); 1791 cairo_rectangle(cr, expose_rect.x(), expose_rect.y(), 1792 expose_rect.width(), expose_rect.height()); 1793 cairo_clip(cr); 1794 1795 // TODO(estade): we probably shouldn't draw the strikethrough on selected 1796 // text. I started to do this, but it was way more effort than it seemed 1797 // worth. 1798 strikethrough_rect.Inset(kStrikethroughStrokeWidth, 1799 kStrikethroughStrokeWidth); 1800 cairo_set_source_rgb(cr, kStrikethroughStrokeRed, 0.0, 0.0); 1801 cairo_set_line_width(cr, kStrikethroughStrokeWidth); 1802 cairo_set_line_cap(cr, CAIRO_LINE_CAP_ROUND); 1803 cairo_move_to(cr, strikethrough_rect.x(), strikethrough_rect.bottom()); 1804 cairo_line_to(cr, strikethrough_rect.right(), strikethrough_rect.y()); 1805 cairo_stroke(cr); 1806 cairo_destroy(cr); 1807 1808 return FALSE; 1809} 1810 1811void OmniboxViewGtk::SelectAllInternal(bool reversed, 1812 bool update_primary_selection) { 1813 GtkTextIter start, end; 1814 if (reversed) { 1815 GetTextBufferBounds(&end, &start); 1816 } else { 1817 GetTextBufferBounds(&start, &end); 1818 } 1819 if (!update_primary_selection) 1820 StartUpdatingHighlightedText(); 1821 gtk_text_buffer_select_range(text_buffer_, &start, &end); 1822 if (!update_primary_selection) 1823 FinishUpdatingHighlightedText(); 1824} 1825 1826void OmniboxViewGtk::StartUpdatingHighlightedText() { 1827 if (gtk_widget_get_realized(text_view_)) { 1828 GtkClipboard* clipboard = 1829 gtk_widget_get_clipboard(text_view_, GDK_SELECTION_PRIMARY); 1830 DCHECK(clipboard); 1831 if (clipboard) 1832 gtk_text_buffer_remove_selection_clipboard(text_buffer_, clipboard); 1833 } 1834 g_signal_handler_block(text_buffer_, mark_set_handler_id_); 1835 g_signal_handler_block(text_buffer_, mark_set_handler_id2_); 1836} 1837 1838void OmniboxViewGtk::FinishUpdatingHighlightedText() { 1839 if (gtk_widget_get_realized(text_view_)) { 1840 GtkClipboard* clipboard = 1841 gtk_widget_get_clipboard(text_view_, GDK_SELECTION_PRIMARY); 1842 DCHECK(clipboard); 1843 if (clipboard) 1844 gtk_text_buffer_add_selection_clipboard(text_buffer_, clipboard); 1845 } 1846 g_signal_handler_unblock(text_buffer_, mark_set_handler_id_); 1847 g_signal_handler_unblock(text_buffer_, mark_set_handler_id2_); 1848} 1849 1850OmniboxViewGtk::CharRange OmniboxViewGtk::GetSelection() const { 1851 // You can not just use get_selection_bounds here, since the order will be 1852 // ascending, and you don't know where the user's start and end of the 1853 // selection was (if the selection was forwards or backwards). Get the 1854 // actual marks so that we can preserve the selection direction. 1855 GtkTextIter start, insert; 1856 GtkTextMark* mark; 1857 1858 mark = gtk_text_buffer_get_selection_bound(text_buffer_); 1859 gtk_text_buffer_get_iter_at_mark(text_buffer_, &start, mark); 1860 1861 mark = gtk_text_buffer_get_insert(text_buffer_); 1862 gtk_text_buffer_get_iter_at_mark(text_buffer_, &insert, mark); 1863 1864 gint start_offset = gtk_text_iter_get_offset(&start); 1865 gint end_offset = gtk_text_iter_get_offset(&insert); 1866 1867 if (supports_pre_edit_) { 1868 // Nothing should be selected when we are in the middle of composition. 1869 DCHECK(pre_edit_.empty() || start_offset == end_offset); 1870 if (!pre_edit_.empty()) { 1871 start_offset += pre_edit_.size(); 1872 end_offset += pre_edit_.size(); 1873 } 1874 } 1875 1876 return CharRange(start_offset, end_offset); 1877} 1878 1879void OmniboxViewGtk::ItersFromCharRange(const CharRange& range, 1880 GtkTextIter* iter_min, 1881 GtkTextIter* iter_max) { 1882 DCHECK(!IsImeComposing()); 1883 gtk_text_buffer_get_iter_at_offset(text_buffer_, iter_min, range.cp_min); 1884 gtk_text_buffer_get_iter_at_offset(text_buffer_, iter_max, range.cp_max); 1885} 1886 1887bool OmniboxViewGtk::IsCaretAtEnd() const { 1888 const CharRange selection = GetSelection(); 1889 return selection.cp_min == selection.cp_max && 1890 selection.cp_min == GetOmniboxTextLength(); 1891} 1892 1893void OmniboxViewGtk::SavePrimarySelection(const std::string& selected_text) { 1894 DCHECK(text_view_); 1895 1896 GtkClipboard* clipboard = 1897 gtk_widget_get_clipboard(text_view_, GDK_SELECTION_PRIMARY); 1898 DCHECK(clipboard); 1899 if (!clipboard) 1900 return; 1901 1902 gtk_clipboard_set_text( 1903 clipboard, selected_text.data(), selected_text.size()); 1904} 1905 1906void OmniboxViewGtk::SetTextAndSelectedRange(const string16& text, 1907 const CharRange& range) { 1908 if (text != GetText()) { 1909 std::string utf8 = UTF16ToUTF8(text); 1910 gtk_text_buffer_set_text(text_buffer_, utf8.data(), utf8.length()); 1911 } 1912 SetSelectedRange(range); 1913 AdjustTextJustification(); 1914} 1915 1916void OmniboxViewGtk::SetSelectedRange(const CharRange& range) { 1917 GtkTextIter insert, bound; 1918 ItersFromCharRange(range, &bound, &insert); 1919 gtk_text_buffer_select_range(text_buffer_, &insert, &bound); 1920 1921 // This should be set *after* setting the selection range, in case setting the 1922 // selection triggers HandleMarkSet which sets |selection_suggested_| to 1923 // false. 1924 selection_suggested_ = true; 1925} 1926 1927void OmniboxViewGtk::AdjustTextJustification() { 1928 DCHECK(text_view_); 1929 1930 PangoDirection content_dir = GetContentDirection(); 1931 1932 // Use keymap direction if content does not have strong direction. 1933 // It matches the behavior of GtkTextView. 1934 if (content_dir == PANGO_DIRECTION_NEUTRAL) { 1935 content_dir = gdk_keymap_get_direction( 1936 gdk_keymap_get_for_display(gtk_widget_get_display(text_view_))); 1937 } 1938 1939 GtkTextDirection widget_dir = gtk_widget_get_direction(text_view_); 1940 1941 if ((widget_dir == GTK_TEXT_DIR_RTL && content_dir == PANGO_DIRECTION_LTR) || 1942 (widget_dir == GTK_TEXT_DIR_LTR && content_dir == PANGO_DIRECTION_RTL)) { 1943 gtk_text_view_set_justification(GTK_TEXT_VIEW(text_view_), 1944 GTK_JUSTIFY_RIGHT); 1945 } else { 1946 gtk_text_view_set_justification(GTK_TEXT_VIEW(text_view_), 1947 GTK_JUSTIFY_LEFT); 1948 } 1949} 1950 1951PangoDirection OmniboxViewGtk::GetContentDirection() { 1952 GtkTextIter iter; 1953 gtk_text_buffer_get_start_iter(text_buffer_, &iter); 1954 1955 PangoDirection dir = PANGO_DIRECTION_NEUTRAL; 1956 do { 1957 dir = pango_unichar_direction(gtk_text_iter_get_char(&iter)); 1958 if (dir != PANGO_DIRECTION_NEUTRAL) 1959 break; 1960 } while (gtk_text_iter_forward_char(&iter)); 1961 1962 return dir; 1963} 1964 1965void OmniboxViewGtk::HandleWidgetDirectionChanged( 1966 GtkWidget* sender, 1967 GtkTextDirection previous_direction) { 1968 AdjustTextJustification(); 1969} 1970 1971void OmniboxViewGtk::HandleDeleteFromCursor(GtkWidget* sender, 1972 GtkDeleteType type, 1973 gint count) { 1974 // If the selected text was suggested for autocompletion, then erase those 1975 // first and then let the default handler take over. 1976 if (selection_suggested_) { 1977 gtk_text_buffer_delete_selection(text_buffer_, true, true); 1978 selection_suggested_ = false; 1979 } 1980} 1981 1982void OmniboxViewGtk::HandleKeymapDirectionChanged(GdkKeymap* sender) { 1983 AdjustTextJustification(); 1984} 1985 1986void OmniboxViewGtk::HandleDeleteRange(GtkTextBuffer* buffer, 1987 GtkTextIter* start, 1988 GtkTextIter* end) { 1989 // Prevent the user from deleting the gray text anchor. We can't simply set 1990 // the gray text anchor readonly by applying a tag with "editable" = FALSE, 1991 // because it'll prevent the insert caret from blinking. 1992 ValidateTextBufferIter(start); 1993 ValidateTextBufferIter(end); 1994 if (!gtk_text_iter_compare(start, end)) { 1995 static guint signal_id = 1996 g_signal_lookup("delete-range", GTK_TYPE_TEXT_BUFFER); 1997 g_signal_stop_emission(buffer, signal_id, 0); 1998 } 1999} 2000 2001void OmniboxViewGtk::HandleMarkSetAlways(GtkTextBuffer* buffer, 2002 GtkTextIter* location, 2003 GtkTextMark* mark) { 2004 if (mark == gray_text_mark_ || !gray_text_mark_) 2005 return; 2006 2007 GtkTextIter new_iter = *location; 2008 ValidateTextBufferIter(&new_iter); 2009 2010 static guint signal_id = g_signal_lookup("mark-set", GTK_TYPE_TEXT_BUFFER); 2011 2012 // "mark-set" signal is actually emitted after the mark's location is already 2013 // set, so if the location is beyond the gray text anchor, we need to move the 2014 // mark again, which will emit the signal again. In order to prevent other 2015 // signal handlers from being called twice, we need to stop signal emission 2016 // before moving the mark again. 2017 if (gtk_text_iter_compare(&new_iter, location)) { 2018 g_signal_stop_emission(buffer, signal_id, 0); 2019 gtk_text_buffer_move_mark(buffer, mark, &new_iter); 2020 return; 2021 } 2022 2023 if (mark != gtk_text_buffer_get_insert(text_buffer_) && 2024 mark != gtk_text_buffer_get_selection_bound(text_buffer_)) { 2025 return; 2026 } 2027 2028 // See issue http://crbug.com/63860 2029 GtkTextIter insert; 2030 GtkTextIter selection_bound; 2031 gtk_text_buffer_get_iter_at_mark(buffer, &insert, 2032 gtk_text_buffer_get_insert(buffer)); 2033 gtk_text_buffer_get_iter_at_mark(buffer, &selection_bound, 2034 gtk_text_buffer_get_selection_bound(buffer)); 2035 2036 GtkTextIter end; 2037 gtk_text_buffer_get_iter_at_mark(text_buffer_, &end, gray_text_mark_); 2038 2039 if (gtk_text_iter_compare(&insert, &end) > 0 || 2040 gtk_text_iter_compare(&selection_bound, &end) > 0) { 2041 g_signal_stop_emission(buffer, signal_id, 0); 2042 } 2043} 2044 2045// static 2046void OmniboxViewGtk::ClipboardGetSelectionThunk( 2047 GtkClipboard* clipboard, 2048 GtkSelectionData* selection_data, 2049 guint info, 2050 gpointer object) { 2051 OmniboxViewGtk* omnibox_view = 2052 reinterpret_cast<OmniboxViewGtk*>( 2053 g_object_get_data(G_OBJECT(object), kOmniboxViewGtkKey)); 2054 omnibox_view->ClipboardGetSelection(clipboard, selection_data, info); 2055} 2056 2057void OmniboxViewGtk::ClipboardGetSelection(GtkClipboard* clipboard, 2058 GtkSelectionData* selection_data, 2059 guint info) { 2060 gtk_selection_data_set_text(selection_data, primary_selection_text_.c_str(), 2061 primary_selection_text_.size()); 2062} 2063 2064std::string OmniboxViewGtk::GetSelectedText() const { 2065 GtkTextIter start, end; 2066 std::string result; 2067 if (gtk_text_buffer_get_selection_bounds(text_buffer_, &start, &end)) { 2068 gchar* text = gtk_text_iter_get_text(&start, &end); 2069 size_t text_len = strlen(text); 2070 if (text_len) 2071 result = std::string(text, text_len); 2072 g_free(text); 2073 } 2074 return result; 2075} 2076 2077void OmniboxViewGtk::UpdatePrimarySelectionIfValidURL() { 2078 string16 text = UTF8ToUTF16(GetSelectedText()); 2079 2080 if (text.empty()) 2081 return; 2082 2083 // Use AdjustTextForCopy to make sure we prefix the text with 'http://'. 2084 CharRange selection = GetSelection(); 2085 GURL url; 2086 bool write_url; 2087 model()->AdjustTextForCopy(selection.selection_min(), IsSelectAll(), &text, 2088 &url, &write_url); 2089 if (write_url) { 2090 selected_text_ = UTF16ToUTF8(text); 2091 OwnPrimarySelection(selected_text_); 2092 } 2093} 2094 2095void OmniboxViewGtk::HandlePreEditChanged(GtkWidget* sender, 2096 const gchar* pre_edit) { 2097 // GtkTextView won't fire "begin-user-action" and "end-user-action" signals 2098 // when changing the pre-edit string, so we need to call 2099 // OnBeforePossibleChange() and OnAfterPossibleChange() by ourselves. 2100 OnBeforePossibleChange(); 2101 if (pre_edit && *pre_edit) { 2102 // GtkTextView will only delete the selection range when committing the 2103 // pre-edit string, which will cause very strange behavior, so we need to 2104 // delete the selection range here explicitly. See http://crbug.com/18808. 2105 if (pre_edit_.empty()) 2106 gtk_text_buffer_delete_selection(text_buffer_, false, true); 2107 pre_edit_ = UTF8ToUTF16(pre_edit); 2108 } else { 2109 pre_edit_.clear(); 2110 } 2111 OnAfterPossibleChange(); 2112} 2113 2114void OmniboxViewGtk::HandleWindowSetFocus(GtkWindow* sender, 2115 GtkWidget* focus) { 2116 // This is actually a guess. If the focused widget changes in "focus-out" 2117 // event handler, then the window will respect that and won't focus 2118 // |focus|. I doubt that is likely to happen however. 2119 going_to_focus_ = focus; 2120} 2121 2122void OmniboxViewGtk::HandleUndoRedo(GtkWidget* sender) { 2123 OnBeforePossibleChange(); 2124} 2125 2126void OmniboxViewGtk::HandleUndoRedoAfter(GtkWidget* sender) { 2127 OnAfterPossibleChange(); 2128} 2129 2130void OmniboxViewGtk::GetTextBufferBounds(GtkTextIter* start, 2131 GtkTextIter* end) const { 2132 gtk_text_buffer_get_start_iter(text_buffer_, start); 2133 gtk_text_buffer_get_iter_at_mark(text_buffer_, end, gray_text_mark_); 2134} 2135 2136void OmniboxViewGtk::ValidateTextBufferIter(GtkTextIter* iter) const { 2137 if (!gray_text_mark_) 2138 return; 2139 2140 GtkTextIter end; 2141 gtk_text_buffer_get_iter_at_mark(text_buffer_, &end, gray_text_mark_); 2142 if (gtk_text_iter_compare(iter, &end) > 0) 2143 *iter = end; 2144} 2145 2146void OmniboxViewGtk::AdjustVerticalAlignmentOfGrayTextView() { 2147 // By default, GtkTextView layouts an anchored child widget just above the 2148 // baseline, so we need to move the |gray_text_view_| down to make sure it 2149 // has the same baseline as the |text_view_|. 2150 PangoLayout* layout = gtk_label_get_layout(GTK_LABEL(gray_text_view_)); 2151 int height; 2152 pango_layout_get_size(layout, NULL, &height); 2153 int baseline = pango_layout_get_baseline(layout); 2154 g_object_set(gray_text_anchor_tag_, "rise", baseline - height, NULL); 2155} 2156