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