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