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