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