diff options
author | tfarina@chromium.org <tfarina@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2011-04-26 22:16:14 +0000 |
---|---|---|
committer | tfarina@chromium.org <tfarina@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2011-04-26 22:16:14 +0000 |
commit | 7dcc272d07e2dc3fb5394439edc2975498c9ce98 (patch) | |
tree | 07b3c6a293d7d80c6e91899bfc19c00756c72cd8 /chrome/browser/autocomplete/autocomplete_edit_view_gtk.cc | |
parent | 51c84ee35f03c61d35c30290d46df332c8ddeae5 (diff) | |
download | chromium_src-7dcc272d07e2dc3fb5394439edc2975498c9ce98.zip chromium_src-7dcc272d07e2dc3fb5394439edc2975498c9ce98.tar.gz chromium_src-7dcc272d07e2dc3fb5394439edc2975498c9ce98.tar.bz2 |
gtk: Move AutocompleteEditViewGtk/autocomplete_edit_view_gtk.* to ui/gtk/omnibox directory.
- Rename AutocompleteEditViewGtk to OmniboxViewGtk.
- Move autocomplete_edit_view_gtk.* to omnibox_view_gtk.*
BUG=80186
TEST=None
R=pkasting@chromium.org,evan@chromium.org
Review URL: http://codereview.chromium.org/6905030
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@83086 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'chrome/browser/autocomplete/autocomplete_edit_view_gtk.cc')
-rw-r--r-- | chrome/browser/autocomplete/autocomplete_edit_view_gtk.cc | 2347 |
1 files changed, 0 insertions, 2347 deletions
diff --git a/chrome/browser/autocomplete/autocomplete_edit_view_gtk.cc b/chrome/browser/autocomplete/autocomplete_edit_view_gtk.cc deleted file mode 100644 index 7bb87ce..0000000 --- a/chrome/browser/autocomplete/autocomplete_edit_view_gtk.cc +++ /dev/null @@ -1,2347 +0,0 @@ -// Copyright (c) 2011 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#include "chrome/browser/autocomplete/autocomplete_edit_view_gtk.h" - -#include <gdk/gdkkeysyms.h> -#include <gtk/gtk.h> - -#include <algorithm> - -#include "base/logging.h" -#include "base/string_util.h" -#include "base/utf_string_conversions.h" -#include "chrome/app/chrome_command_ids.h" -#include "chrome/browser/autocomplete/autocomplete_edit.h" -#include "chrome/browser/autocomplete/autocomplete_match.h" -#include "chrome/browser/autocomplete/autocomplete_popup_model.h" -#include "chrome/browser/bookmarks/bookmark_node_data.h" -#include "chrome/browser/command_updater.h" -#include "chrome/browser/defaults.h" -#include "chrome/browser/instant/instant_controller.h" -#include "chrome/browser/platform_util.h" -#include "chrome/browser/ui/gtk/gtk_util.h" -#include "chrome/browser/ui/gtk/view_id_util.h" -#include "chrome/browser/ui/toolbar/toolbar_model.h" -#include "content/browser/tab_contents/tab_contents.h" -#include "content/common/notification_service.h" -#include "googleurl/src/gurl.h" -#include "grit/generated_resources.h" -#include "net/base/escape.h" -#include "third_party/undoview/undo_view.h" -#include "ui/base/animation/multi_animation.h" -#include "ui/base/dragdrop/drag_drop_types.h" -#include "ui/base/l10n/l10n_util.h" -#include "ui/base/resource/resource_bundle.h" -#include "ui/gfx/color_utils.h" -#include "ui/gfx/font.h" -#include "ui/gfx/gtk_util.h" -#include "ui/gfx/skia_utils_gtk.h" - -#if defined(TOOLKIT_VIEWS) -#include "chrome/browser/autocomplete/autocomplete_edit_view_views.h" -#include "chrome/browser/ui/views/autocomplete/autocomplete_popup_contents_view.h" -#include "chrome/browser/ui/views/location_bar/location_bar_view.h" -#include "views/controls/textfield/native_textfield_views.h" -#include "views/events/event.h" -#else -#include "chrome/browser/autocomplete/autocomplete_popup_view_gtk.h" -#include "chrome/browser/ui/gtk/gtk_theme_service.h" -#include "chrome/browser/ui/gtk/location_bar_view_gtk.h" -#endif - -namespace { - -const gchar* kAutocompleteEditViewGtkKey = "__ACE_VIEW_GTK__"; - -const char kTextBaseColor[] = "#808080"; -const char kSecureSchemeColor[] = "#079500"; -const char kSecurityErrorSchemeColor[] = "#a20000"; - -const double kStrikethroughStrokeRed = 162.0 / 256.0; -const double kStrikethroughStrokeWidth = 2.0; - -size_t GetUTF8Offset(const string16& text, size_t text_offset) { - return UTF16ToUTF8(text.substr(0, text_offset)).size(); -} - -// A helper method for determining a valid drag operation given the allowed -// operation. We prefer copy over link. -int CopyOrLinkDragOperation(int drag_operation) { - if (drag_operation & ui::DragDropTypes::DRAG_COPY) - return ui::DragDropTypes::DRAG_COPY; - if (drag_operation & ui::DragDropTypes::DRAG_LINK) - return ui::DragDropTypes::DRAG_LINK; - return ui::DragDropTypes::DRAG_NONE; -} - -// Stores GTK+-specific state so it can be restored after switching tabs. -struct ViewState { - explicit ViewState(const AutocompleteEditViewGtk::CharRange& selection_range) - : selection_range(selection_range) { - } - - // Range of selected text. - AutocompleteEditViewGtk::CharRange selection_range; -}; - -struct AutocompleteEditState { - AutocompleteEditState(const AutocompleteEditModel::State& model_state, - const ViewState& view_state) - : model_state(model_state), - view_state(view_state) { - } - - const AutocompleteEditModel::State model_state; - const ViewState view_state; -}; - -// Returns a lazily initialized property bag accessor for saving our state in a -// TabContents. -PropertyAccessor<AutocompleteEditState>* GetStateAccessor() { - static PropertyAccessor<AutocompleteEditState> state; - return &state; -} - -// Set up style properties to override the default GtkTextView; if a theme has -// overridden some of these properties, an inner-line will be displayed inside -// the fake GtkTextEntry. -void SetEntryStyle() { - static bool style_was_set = false; - - if (style_was_set) - return; - style_was_set = true; - - gtk_rc_parse_string( - "style \"chrome-location-bar-entry\" {" - " xthickness = 0\n" - " ythickness = 0\n" - " GtkWidget::focus_padding = 0\n" - " GtkWidget::focus-line-width = 0\n" - " GtkWidget::interior_focus = 0\n" - " GtkWidget::internal-padding = 0\n" - " GtkContainer::border-width = 0\n" - "}\n" - "widget \"*chrome-location-bar-entry\" " - "style \"chrome-location-bar-entry\""); -} - -// Copied from GTK+. Called when we lose the primary selection. This will clear -// the selection in the text buffer. -void ClipboardSelectionCleared(GtkClipboard* clipboard, - gpointer data) { - GtkTextIter insert; - GtkTextIter selection_bound; - GtkTextBuffer* buffer = GTK_TEXT_BUFFER(data); - - gtk_text_buffer_get_iter_at_mark(buffer, &insert, - gtk_text_buffer_get_insert(buffer)); - gtk_text_buffer_get_iter_at_mark(buffer, &selection_bound, - gtk_text_buffer_get_selection_bound(buffer)); - - if (!gtk_text_iter_equal(&insert, &selection_bound)) { - gtk_text_buffer_move_mark(buffer, - gtk_text_buffer_get_selection_bound(buffer), - &insert); - } -} - -} // namespace - -AutocompleteEditViewGtk::AutocompleteEditViewGtk( - AutocompleteEditController* controller, - ToolbarModel* toolbar_model, - Profile* profile, - CommandUpdater* command_updater, - bool popup_window_mode, -#if defined(TOOLKIT_VIEWS) - views::View* location_bar) -#else - GtkWidget* location_bar) -#endif - : text_view_(NULL), - tag_table_(NULL), - text_buffer_(NULL), - faded_text_tag_(NULL), - secure_scheme_tag_(NULL), - security_error_scheme_tag_(NULL), - normal_text_tag_(NULL), - instant_anchor_tag_(NULL), - instant_view_(NULL), - instant_mark_(NULL), - model_(new AutocompleteEditModel(this, controller, profile)), - controller_(controller), - toolbar_model_(toolbar_model), - command_updater_(command_updater), - popup_window_mode_(popup_window_mode), - security_level_(ToolbarModel::NONE), - mark_set_handler_id_(0), -#if defined(OS_CHROMEOS) - button_1_pressed_(false), - text_selected_during_click_(false), - text_view_focused_before_button_press_(false), -#endif -#if defined(TOOLKIT_VIEWS) - location_bar_view_(location_bar), -#else - theme_service_(GtkThemeService::GetFrom(profile)), -#endif - enter_was_pressed_(false), - tab_was_pressed_(false), - paste_clipboard_requested_(false), - enter_was_inserted_(false), - selection_suggested_(false), - delete_was_pressed_(false), - delete_at_end_pressed_(false), - handling_key_press_(false), - content_maybe_changed_by_key_press_(false), - update_popup_without_focus_(false), -#if GTK_CHECK_VERSION(2, 20, 0) - preedit_size_before_change_(0), -#endif - going_to_focus_(NULL) { - popup_view_.reset( -#if defined(TOOLKIT_VIEWS) - new AutocompletePopupContentsView -#else - new AutocompletePopupViewGtk -#endif - (GetFont(), this, model_.get(), profile, location_bar)); -} - -AutocompleteEditViewGtk::~AutocompleteEditViewGtk() { - NotificationService::current()->Notify( - NotificationType::AUTOCOMPLETE_EDIT_DESTROYED, - Source<AutocompleteEditViewGtk>(this), - NotificationService::NoDetails()); - - // Explicitly teardown members which have a reference to us. Just to be safe - // we want them to be destroyed before destroying any other internal state. - popup_view_.reset(); - model_.reset(); - - // We own our widget and TextView related objects. - if (alignment_.get()) { // Init() has been called. - alignment_.Destroy(); - g_object_unref(text_buffer_); - g_object_unref(tag_table_); - // The tags we created are owned by the tag_table, and should be destroyed - // along with it. We don't hold our own reference to them. - } -} - -void AutocompleteEditViewGtk::Init() { - SetEntryStyle(); - - // The height of the text view is going to change based on the font used. We - // don't want to stretch the height, and we want it vertically centered. - alignment_.Own(gtk_alignment_new(0., 0.5, 1.0, 0.0)); - gtk_widget_set_name(alignment_.get(), - "chrome-autocomplete-edit-view"); - - // The GtkTagTable and GtkTextBuffer are not initially unowned, so we have - // our own reference when we create them, and we own them. Adding them to - // the other objects adds a reference; it doesn't adopt them. - tag_table_ = gtk_text_tag_table_new(); - text_buffer_ = gtk_text_buffer_new(tag_table_); - g_object_set_data(G_OBJECT(text_buffer_), kAutocompleteEditViewGtkKey, this); - - // We need to run this two handlers before undo manager's handlers, so that - // text iterators modified by these handlers can be passed down to undo - // manager's handlers. - g_signal_connect(text_buffer_, "delete-range", - G_CALLBACK(&HandleDeleteRangeThunk), this); - g_signal_connect(text_buffer_, "mark-set", - G_CALLBACK(&HandleMarkSetAlwaysThunk), this); - - text_view_ = gtk_undo_view_new(text_buffer_); - if (popup_window_mode_) - gtk_text_view_set_editable(GTK_TEXT_VIEW(text_view_), false); - - // One pixel left margin is necessary to make the cursor visible when UI - // language direction is LTR but |text_buffer_|'s content direction is RTL. - gtk_text_view_set_left_margin(GTK_TEXT_VIEW(text_view_), 1); - - // See SetEntryStyle() comments. - gtk_widget_set_name(text_view_, "chrome-location-bar-entry"); - - // The text view was floating. It will now be owned by the alignment. - gtk_container_add(GTK_CONTAINER(alignment_.get()), text_view_); - - // Do not allow inserting tab characters when pressing Tab key, so that when - // Tab key is pressed, |text_view_| will emit "move-focus" signal, which will - // be intercepted by our own handler to trigger Tab to search feature when - // necessary. - gtk_text_view_set_accepts_tab(GTK_TEXT_VIEW(text_view_), FALSE); - - faded_text_tag_ = gtk_text_buffer_create_tag(text_buffer_, - NULL, "foreground", kTextBaseColor, NULL); - secure_scheme_tag_ = gtk_text_buffer_create_tag(text_buffer_, - NULL, "foreground", kSecureSchemeColor, NULL); - security_error_scheme_tag_ = gtk_text_buffer_create_tag(text_buffer_, - NULL, "foreground", kSecurityErrorSchemeColor, NULL); - normal_text_tag_ = gtk_text_buffer_create_tag(text_buffer_, - NULL, "foreground", "#000000", NULL); - - // NOTE: This code used to connect to "changed", however this was fired too - // often and during bad times (our own buffer changes?). It works out much - // better to listen to end-user-action, which should be fired whenever the - // user makes some sort of change to the buffer. - g_signal_connect(text_buffer_, "begin-user-action", - G_CALLBACK(&HandleBeginUserActionThunk), this); - g_signal_connect(text_buffer_, "end-user-action", - G_CALLBACK(&HandleEndUserActionThunk), this); - // We connect to key press and release for special handling of a few keys. - g_signal_connect(text_view_, "key-press-event", - G_CALLBACK(&HandleKeyPressThunk), this); - g_signal_connect(text_view_, "key-release-event", - G_CALLBACK(&HandleKeyReleaseThunk), this); - g_signal_connect(text_view_, "button-press-event", - G_CALLBACK(&HandleViewButtonPressThunk), this); - g_signal_connect(text_view_, "button-release-event", - G_CALLBACK(&HandleViewButtonReleaseThunk), this); - g_signal_connect(text_view_, "focus-in-event", - G_CALLBACK(&HandleViewFocusInThunk), this); - g_signal_connect(text_view_, "focus-out-event", - G_CALLBACK(&HandleViewFocusOutThunk), this); - // NOTE: The GtkTextView documentation asks you not to connect to this - // signal, but it is very convenient and clean for catching up/down. - g_signal_connect(text_view_, "move-cursor", - G_CALLBACK(&HandleViewMoveCursorThunk), this); - g_signal_connect(text_view_, "move-focus", - G_CALLBACK(&HandleViewMoveFocusThunk), this); - // Override the size request. We want to keep the original height request - // from the widget, since that's font dependent. We want to ignore the width - // so we don't force a minimum width based on the text length. - g_signal_connect(text_view_, "size-request", - G_CALLBACK(&HandleViewSizeRequestThunk), this); - g_signal_connect(text_view_, "populate-popup", - G_CALLBACK(&HandlePopulatePopupThunk), this); - mark_set_handler_id_ = g_signal_connect( - text_buffer_, "mark-set", G_CALLBACK(&HandleMarkSetThunk), this); - mark_set_handler_id2_ = g_signal_connect_after( - text_buffer_, "mark-set", G_CALLBACK(&HandleMarkSetAfterThunk), this); - g_signal_connect(text_view_, "drag-data-received", - G_CALLBACK(&HandleDragDataReceivedThunk), this); - // Override the text_view_'s default drag-data-get handler by calling our own - // version after the normal call has happened. - g_signal_connect_after(text_view_, "drag-data-get", - G_CALLBACK(&HandleDragDataGetThunk), this); - g_signal_connect(text_view_, "backspace", - G_CALLBACK(&HandleBackSpaceThunk), this); - g_signal_connect(text_view_, "copy-clipboard", - G_CALLBACK(&HandleCopyClipboardThunk), this); - g_signal_connect(text_view_, "cut-clipboard", - G_CALLBACK(&HandleCutClipboardThunk), this); - g_signal_connect(text_view_, "paste-clipboard", - G_CALLBACK(&HandlePasteClipboardThunk), this); - g_signal_connect_after(text_view_, "expose-event", - G_CALLBACK(&HandleExposeEventThunk), this); - g_signal_connect(text_view_, "direction-changed", - G_CALLBACK(&HandleWidgetDirectionChangedThunk), this); - g_signal_connect(text_view_, "delete-from-cursor", - G_CALLBACK(&HandleDeleteFromCursorThunk), this); - g_signal_connect(text_view_, "hierarchy-changed", - G_CALLBACK(&HandleHierarchyChangedThunk), this); -#if GTK_CHECK_VERSION(2, 20, 0) - g_signal_connect(text_view_, "preedit-changed", - G_CALLBACK(&HandlePreeditChangedThunk), this); -#endif - g_signal_connect(text_view_, "undo", G_CALLBACK(&HandleUndoRedoThunk), this); - g_signal_connect(text_view_, "redo", G_CALLBACK(&HandleUndoRedoThunk), this); - g_signal_connect_after(text_view_, "undo", - G_CALLBACK(&HandleUndoRedoAfterThunk), this); - g_signal_connect_after(text_view_, "redo", - G_CALLBACK(&HandleUndoRedoAfterThunk), this); - g_signal_connect(text_view_, "destroy", - G_CALLBACK(>k_widget_destroyed), &text_view_); - - // Setup for the Instant suggestion text view. - // GtkLabel is used instead of GtkTextView to get transparent background. - instant_view_ = gtk_label_new(NULL); - gtk_widget_set_no_show_all(instant_view_, TRUE); - gtk_label_set_selectable(GTK_LABEL(instant_view_), TRUE); - - GtkTextIter end_iter; - gtk_text_buffer_get_end_iter(text_buffer_, &end_iter); - - // Insert a Zero Width Space character just before the instant anchor. - // It's a hack to workaround a bug of GtkTextView which can not align the - // preedit string and a child anchor correctly when there is no other content - // around the preedit string. - gtk_text_buffer_insert(text_buffer_, &end_iter, "\342\200\213", -1); - GtkTextChildAnchor* instant_anchor = - gtk_text_buffer_create_child_anchor(text_buffer_, &end_iter); - - gtk_text_view_add_child_at_anchor(GTK_TEXT_VIEW(text_view_), - instant_view_, - instant_anchor); - - instant_anchor_tag_ = gtk_text_buffer_create_tag(text_buffer_, NULL, NULL); - - GtkTextIter anchor_iter; - gtk_text_buffer_get_iter_at_child_anchor(text_buffer_, &anchor_iter, - instant_anchor); - gtk_text_buffer_apply_tag(text_buffer_, instant_anchor_tag_, - &anchor_iter, &end_iter); - - GtkTextIter start_iter; - gtk_text_buffer_get_start_iter(text_buffer_, &start_iter); - instant_mark_ = - gtk_text_buffer_create_mark(text_buffer_, NULL, &start_iter, FALSE); - - // Hooking up this handler after setting up above hacks for Instant view, so - // that we won't filter out the special ZWP mark itself. - g_signal_connect(text_buffer_, "insert-text", - G_CALLBACK(&HandleInsertTextThunk), this); - - AdjustVerticalAlignmentOfInstantView(); - - ui::MultiAnimation::Parts parts; - parts.push_back(ui::MultiAnimation::Part( - InstantController::kAutoCommitPauseTimeMS, ui::Tween::ZERO)); - parts.push_back(ui::MultiAnimation::Part( - InstantController::kAutoCommitFadeInTimeMS, ui::Tween::EASE_IN)); - instant_animation_.reset(new ui::MultiAnimation(parts)); - instant_animation_->set_continuous(false); - -#if !defined(TOOLKIT_VIEWS) - registrar_.Add(this, - NotificationType::BROWSER_THEME_CHANGED, - NotificationService::AllSources()); - theme_service_->InitThemesFor(this); -#else - // Manually invoke SetBaseColor() because TOOLKIT_VIEWS doesn't observe - // themes. - SetBaseColor(); -#endif - - ViewIDUtil::SetID(GetNativeView(), VIEW_ID_AUTOCOMPLETE); -} - -void AutocompleteEditViewGtk::HandleHierarchyChanged( - GtkWidget* sender, GtkWidget* old_toplevel) { - GtkWindow* new_toplevel = platform_util::GetTopLevel(sender); - if (!new_toplevel) - return; - - // Use |signals_| to make sure we don't get called back after destruction. - signals_.Connect(new_toplevel, "set-focus", - G_CALLBACK(&HandleWindowSetFocusThunk), this); -} - -void AutocompleteEditViewGtk::SetFocus() { - DCHECK(text_view_); - gtk_widget_grab_focus(text_view_); -} - -int AutocompleteEditViewGtk::WidthOfTextAfterCursor() { - // Not used. - return -1; -} - -AutocompleteEditModel* AutocompleteEditViewGtk::model() { - return model_.get(); -} - -const AutocompleteEditModel* AutocompleteEditViewGtk::model() const { - return model_.get(); -} - -void AutocompleteEditViewGtk::SaveStateToTab(TabContents* tab) { - DCHECK(tab); - // If any text has been selected, register it as the PRIMARY selection so it - // can still be pasted via middle-click after the text view is cleared. - if (!selected_text_.empty()) - SavePrimarySelection(selected_text_); - // NOTE: GetStateForTabSwitch may affect GetSelection, so order is important. - AutocompleteEditModel::State model_state = model_->GetStateForTabSwitch(); - GetStateAccessor()->SetProperty( - tab->property_bag(), - AutocompleteEditState(model_state, ViewState(GetSelection()))); -} - -void AutocompleteEditViewGtk::Update(const TabContents* contents) { - // NOTE: We're getting the URL text here from the ToolbarModel. - bool visibly_changed_permanent_text = - model_->UpdatePermanentText(WideToUTF16Hack(toolbar_model_->GetText())); - - ToolbarModel::SecurityLevel security_level = - toolbar_model_->GetSecurityLevel(); - bool changed_security_level = (security_level != security_level_); - security_level_ = security_level; - - if (contents) { - selected_text_.clear(); - RevertAll(); - const AutocompleteEditState* state = - GetStateAccessor()->GetProperty(contents->property_bag()); - if (state) { - model_->RestoreState(state->model_state); - - // Move the marks for the cursor and the other end of the selection to - // the previously-saved offsets (but preserve PRIMARY). - StartUpdatingHighlightedText(); - SetSelectedRange(state->view_state.selection_range); - FinishUpdatingHighlightedText(); - } - } else if (visibly_changed_permanent_text) { - RevertAll(); - // TODO(deanm): There should be code to restore select all here. - } else if (changed_security_level) { - EmphasizeURLComponents(); - } -} - -void AutocompleteEditViewGtk::OpenURL(const GURL& url, - WindowOpenDisposition disposition, - PageTransition::Type transition, - const GURL& alternate_nav_url, - size_t selected_line, - const string16& keyword) { - if (!url.is_valid()) - return; - - model_->OpenURL(url, disposition, transition, alternate_nav_url, - selected_line, keyword); -} - -string16 AutocompleteEditViewGtk::GetText() const { - GtkTextIter start, end; - GetTextBufferBounds(&start, &end); - gchar* utf8 = gtk_text_buffer_get_text(text_buffer_, &start, &end, false); - string16 out(UTF8ToUTF16(utf8)); - g_free(utf8); - -#if GTK_CHECK_VERSION(2, 20, 0) - // We need to treat the text currently being composed by the input method as - // part of the text content, so that omnibox can work correctly in the middle - // of composition. - if (preedit_.size()) { - GtkTextMark* mark = gtk_text_buffer_get_insert(text_buffer_); - gtk_text_buffer_get_iter_at_mark(text_buffer_, &start, mark); - out.insert(gtk_text_iter_get_offset(&start), preedit_); - } -#endif - return out; -} - -bool AutocompleteEditViewGtk::IsEditingOrEmpty() const { - return model_->user_input_in_progress() || (GetTextLength() == 0); -} - -int AutocompleteEditViewGtk::GetIcon() const { - return IsEditingOrEmpty() ? - AutocompleteMatch::TypeToIcon(model_->CurrentTextType()) : - toolbar_model_->GetIcon(); -} - -void AutocompleteEditViewGtk::SetUserText(const string16& text) { - SetUserText(text, text, true); -} - -void AutocompleteEditViewGtk::SetUserText(const string16& text, - const string16& display_text, - bool update_popup) { - model_->SetUserText(text); - // TODO(deanm): something about selection / focus change here. - SetWindowTextAndCaretPos(display_text, display_text.length()); - if (update_popup) - UpdatePopup(); - TextChanged(); -} - -void AutocompleteEditViewGtk::SetWindowTextAndCaretPos(const string16& text, - size_t caret_pos) { - CharRange range(static_cast<int>(caret_pos), static_cast<int>(caret_pos)); - SetTextAndSelectedRange(text, range); -} - -void AutocompleteEditViewGtk::SetForcedQuery() { - const string16 current_text(GetText()); - const size_t start = current_text.find_first_not_of(kWhitespaceUTF16); - if (start == string16::npos || (current_text[start] != '?')) { - SetUserText(ASCIIToUTF16("?")); - } else { - StartUpdatingHighlightedText(); - SetSelectedRange(CharRange(current_text.size(), start + 1)); - FinishUpdatingHighlightedText(); - } -} - -bool AutocompleteEditViewGtk::IsSelectAll() { - GtkTextIter sel_start, sel_end; - gtk_text_buffer_get_selection_bounds(text_buffer_, &sel_start, &sel_end); - - GtkTextIter start, end; - GetTextBufferBounds(&start, &end); - - // Returns true if the |text_buffer_| is empty. - return gtk_text_iter_equal(&start, &sel_start) && - gtk_text_iter_equal(&end, &sel_end); -} - -bool AutocompleteEditViewGtk::DeleteAtEndPressed() { - return delete_at_end_pressed_; -} - -void AutocompleteEditViewGtk::GetSelectionBounds(string16::size_type* start, - string16::size_type* end) { - CharRange selection = GetSelection(); - *start = static_cast<size_t>(selection.cp_min); - *end = static_cast<size_t>(selection.cp_max); -} - -void AutocompleteEditViewGtk::SelectAll(bool reversed) { - // SelectAll() is invoked as a side effect of other actions (e.g. switching - // tabs or hitting Escape) in autocomplete_edit.cc, so we don't update the - // PRIMARY selection here. - SelectAllInternal(reversed, false); -} - -void AutocompleteEditViewGtk::RevertAll() { - ClosePopup(); - model_->Revert(); - TextChanged(); -} - -void AutocompleteEditViewGtk::UpdatePopup() { - model_->SetInputInProgress(true); - if (!update_popup_without_focus_ && !model_->has_focus()) - return; - - // Don't inline autocomplete when the caret/selection isn't at the end of - // the text, or in the middle of composition. - CharRange sel = GetSelection(); - bool no_inline_autocomplete = - std::max(sel.cp_max, sel.cp_min) < GetTextLength() || IsImeComposing(); - model_->StartAutocomplete(sel.cp_min != sel.cp_max, no_inline_autocomplete); -} - -void AutocompleteEditViewGtk::ClosePopup() { - model_->StopAutocomplete(); -} - -void AutocompleteEditViewGtk::OnTemporaryTextMaybeChanged( - const string16& display_text, - bool save_original_selection) { - if (save_original_selection) - saved_temporary_selection_ = GetSelection(); - - StartUpdatingHighlightedText(); - SetWindowTextAndCaretPos(display_text, display_text.length()); - FinishUpdatingHighlightedText(); - TextChanged(); -} - -bool AutocompleteEditViewGtk::OnInlineAutocompleteTextMaybeChanged( - const string16& display_text, - size_t user_text_length) { - if (display_text == GetText()) - return false; - - StartUpdatingHighlightedText(); - CharRange range(display_text.size(), user_text_length); - SetTextAndSelectedRange(display_text, range); - FinishUpdatingHighlightedText(); - TextChanged(); - return true; -} - -void AutocompleteEditViewGtk::OnRevertTemporaryText() { - StartUpdatingHighlightedText(); - SetSelectedRange(saved_temporary_selection_); - FinishUpdatingHighlightedText(); - TextChanged(); -} - -void AutocompleteEditViewGtk::OnBeforePossibleChange() { - // Record this paste, so we can do different behavior. - if (paste_clipboard_requested_) { - paste_clipboard_requested_ = false; - model_->on_paste(); - } - - // This method will be called in HandleKeyPress() method just before - // handling a key press event. So we should prevent it from being called - // when handling the key press event. - if (handling_key_press_) - return; - - // Record our state. - text_before_change_ = GetText(); - sel_before_change_ = GetSelection(); -#if GTK_CHECK_VERSION(2, 20, 0) - preedit_size_before_change_ = preedit_.size(); -#endif -} - -// TODO(deanm): This is mostly stolen from Windows, and will need some work. -bool AutocompleteEditViewGtk::OnAfterPossibleChange() { - // This method will be called in HandleKeyPress() method just after - // handling a key press event. So we should prevent it from being called - // when handling the key press event. - if (handling_key_press_) { - content_maybe_changed_by_key_press_ = true; - return false; - } - - // If the change is caused by an Enter key press event, and the event was not - // handled by IME, then it's an unexpected change and shall be reverted here. - // {Start|Finish}UpdatingHighlightedText() are called here to prevent the - // PRIMARY selection from being changed. - if (enter_was_pressed_ && enter_was_inserted_) { - StartUpdatingHighlightedText(); - SetTextAndSelectedRange(text_before_change_, sel_before_change_); - FinishUpdatingHighlightedText(); - return false; - } - - const CharRange new_sel = GetSelection(); - const int length = GetTextLength(); - const bool selection_differs = - ((new_sel.cp_min != new_sel.cp_max) || - (sel_before_change_.cp_min != sel_before_change_.cp_max)) && - ((new_sel.cp_min != sel_before_change_.cp_min) || - (new_sel.cp_max != sel_before_change_.cp_max)); - const bool at_end_of_edit = - (new_sel.cp_min == length && new_sel.cp_max == length); - - // See if the text or selection have changed since OnBeforePossibleChange(). - const string16 new_text(GetText()); - text_changed_ = (new_text != text_before_change_); -#if GTK_CHECK_VERSION(2, 20, 0) - text_changed_ = - text_changed_ || (preedit_.size() != preedit_size_before_change_); -#endif - - if (text_changed_) - AdjustTextJustification(); - - // When the user has deleted text, we don't allow inline autocomplete. Make - // sure to not flag cases like selecting part of the text and then pasting - // (or typing) the prefix of that selection. (We detect these by making - // sure the caret, which should be after any insertion, hasn't moved - // forward of the old selection start.) - const bool just_deleted_text = - (text_before_change_.length() > new_text.length()) && - (new_sel.cp_min <= std::min(sel_before_change_.cp_min, - sel_before_change_.cp_max)); - - delete_at_end_pressed_ = false; - - const bool something_changed = model_->OnAfterPossibleChange( - new_text, new_sel.selection_min(), new_sel.selection_max(), - selection_differs, text_changed_, just_deleted_text, - !IsImeComposing()); - - // If only selection was changed, we don't need to call |controller_|'s - // OnChanged() method, which is called in TextChanged(). - // But we still need to call EmphasizeURLComponents() to make sure the text - // attributes are updated correctly. - if (something_changed && text_changed_) { - TextChanged(); - } else if (selection_differs) { - EmphasizeURLComponents(); - } else if (delete_was_pressed_ && at_end_of_edit) { - delete_at_end_pressed_ = true; - model_->OnChanged(); - } - delete_was_pressed_ = false; - - return something_changed; -} - -gfx::NativeView AutocompleteEditViewGtk::GetNativeView() const { - return alignment_.get(); -} - -CommandUpdater* AutocompleteEditViewGtk::GetCommandUpdater() { - return command_updater_; -} - -void AutocompleteEditViewGtk::SetInstantSuggestion(const string16& suggestion, - bool animate_to_complete) { - std::string suggestion_utf8 = UTF16ToUTF8(suggestion); - - gtk_label_set_text(GTK_LABEL(instant_view_), suggestion_utf8.c_str()); - - StopAnimation(); - - if (suggestion.empty()) { - gtk_widget_hide(instant_view_); - return; - } - if (animate_to_complete -#if GTK_CHECK_VERSION(2, 20, 0) - && preedit_.empty() -#endif - ) { - instant_animation_->set_delegate(this); - instant_animation_->Start(); - } - - gtk_widget_show(instant_view_); - AdjustVerticalAlignmentOfInstantView(); - UpdateInstantViewColors(); -} - -string16 AutocompleteEditViewGtk::GetInstantSuggestion() const { - const gchar* suggestion = gtk_label_get_text(GTK_LABEL(instant_view_)); - return suggestion ? UTF8ToUTF16(suggestion) : string16(); -} - -int AutocompleteEditViewGtk::TextWidth() const { - // TextWidth may be called after gtk widget tree is destroyed but - // before AutocompleteEditViewGtk gets deleted. This is a safe guard - // to avoid accessing |text_view_| that has already been destroyed. - // See crbug.com/70192. - if (!text_view_) - return 0; - - int horizontal_border_size = - gtk_text_view_get_border_window_size(GTK_TEXT_VIEW(text_view_), - GTK_TEXT_WINDOW_LEFT) + - gtk_text_view_get_border_window_size(GTK_TEXT_VIEW(text_view_), - GTK_TEXT_WINDOW_RIGHT) + - gtk_text_view_get_left_margin(GTK_TEXT_VIEW(text_view_)) + - gtk_text_view_get_right_margin(GTK_TEXT_VIEW(text_view_)); - - GtkTextIter start, end; - GdkRectangle first_char_bounds, last_char_bounds; - gtk_text_buffer_get_start_iter(text_buffer_, &start); - - // Use the real end iterator here to take the width of instant suggestion - // text into account, so that location bar can layout its children correctly. - gtk_text_buffer_get_end_iter(text_buffer_, &end); - gtk_text_view_get_iter_location(GTK_TEXT_VIEW(text_view_), - &start, &first_char_bounds); - gtk_text_view_get_iter_location(GTK_TEXT_VIEW(text_view_), - &end, &last_char_bounds); - - gint first_char_start = first_char_bounds.x; - gint first_char_end = first_char_start + first_char_bounds.width; - gint last_char_start = last_char_bounds.x; - gint last_char_end = last_char_start + last_char_bounds.width; - - // bounds width could be negative for RTL text. - if (first_char_start > first_char_end) - std::swap(first_char_start, first_char_end); - if (last_char_start > last_char_end) - std::swap(last_char_start, last_char_end); - - gint text_width = first_char_start < last_char_start ? - last_char_end - first_char_start : first_char_end - last_char_start; - - return text_width + horizontal_border_size; -} - -bool AutocompleteEditViewGtk::IsImeComposing() const { -#if GTK_CHECK_VERSION(2, 20, 0) - return !preedit_.empty(); -#else - return false; -#endif -} - -#if defined(TOOLKIT_VIEWS) -views::View* AutocompleteEditViewGtk::AddToView(views::View* parent) { - views::NativeViewHost* host = new views::NativeViewHost; - parent->AddChildView(host); - host->set_focus_view(parent); - host->Attach(GetNativeView()); - return host; -} - -int AutocompleteEditViewGtk::OnPerformDrop( - const views::DropTargetEvent& event) { - string16 text; - const ui::OSExchangeData& data = event.data(); - if (data.HasURL()) { - GURL url; - string16 title; - if (data.GetURLAndTitle(&url, &title)) - text = UTF8ToUTF16(url.spec()); - } else { - string16 data_string; - if (data.GetString(&data_string)) - text = CollapseWhitespace(data_string, true); - } - - if (!text.empty() && OnPerformDropImpl(text)) - return CopyOrLinkDragOperation(event.source_operations()); - - return ui::DragDropTypes::DRAG_NONE; -} - -// static -AutocompleteEditView* AutocompleteEditViewGtk::Create( - AutocompleteEditController* controller, - ToolbarModel* toolbar_model, - Profile* profile, - CommandUpdater* command_updater, - bool popup_window_mode, - views::View* location_bar) { - if (views::NativeTextfieldViews::IsTextfieldViewsEnabled()) { - AutocompleteEditViewViews* autocomplete = - new AutocompleteEditViewViews(controller, - toolbar_model, - profile, - command_updater, - popup_window_mode, - location_bar); - autocomplete->Init(); - return autocomplete; - } - - AutocompleteEditViewGtk* autocomplete = - new AutocompleteEditViewGtk(controller, - toolbar_model, - profile, - command_updater, - popup_window_mode, - location_bar); - autocomplete->Init(); - - // Make all the children of the widget visible. NOTE: this won't display - // anything, it just toggles the visible flag. - gtk_widget_show_all(autocomplete->GetNativeView()); - // Hide the widget. NativeViewHostGtk will make it visible again as - // necessary. - gtk_widget_hide(autocomplete->GetNativeView()); - - return autocomplete; -} -#endif - -void AutocompleteEditViewGtk::Observe(NotificationType type, - const NotificationSource& source, - const NotificationDetails& details) { - DCHECK(type == NotificationType::BROWSER_THEME_CHANGED); - - SetBaseColor(); -} - -void AutocompleteEditViewGtk::AnimationEnded(const ui::Animation* animation) { - model_->CommitSuggestedText(false); -} - -void AutocompleteEditViewGtk::AnimationProgressed( - const ui::Animation* animation) { - UpdateInstantViewColors(); -} - -void AutocompleteEditViewGtk::AnimationCanceled( - const ui::Animation* animation) { - UpdateInstantViewColors(); -} - -void AutocompleteEditViewGtk::SetBaseColor() { - DCHECK(text_view_); - -#if defined(TOOLKIT_VIEWS) - bool use_gtk = false; -#else - bool use_gtk = theme_service_->UseGtkTheme(); -#endif - if (use_gtk) { - gtk_widget_modify_cursor(text_view_, NULL, NULL); - gtk_widget_modify_base(text_view_, GTK_STATE_NORMAL, NULL); - gtk_widget_modify_base(text_view_, GTK_STATE_SELECTED, NULL); - gtk_widget_modify_text(text_view_, GTK_STATE_SELECTED, NULL); - gtk_widget_modify_base(text_view_, GTK_STATE_ACTIVE, NULL); - gtk_widget_modify_text(text_view_, GTK_STATE_ACTIVE, NULL); - - gtk_util::UndoForceFontSize(text_view_); - gtk_util::UndoForceFontSize(instant_view_); - - // Grab the text colors out of the style and set our tags to use them. - GtkStyle* style = gtk_rc_get_style(text_view_); - - // style may be unrealized at this point, so calculate the halfway point - // between text[] and base[] manually instead of just using text_aa[]. - GdkColor average_color = gtk_util::AverageColors( - style->text[GTK_STATE_NORMAL], style->base[GTK_STATE_NORMAL]); - - g_object_set(faded_text_tag_, "foreground-gdk", &average_color, NULL); - g_object_set(normal_text_tag_, "foreground-gdk", - &style->text[GTK_STATE_NORMAL], NULL); - } else { - const GdkColor* background_color_ptr; -#if defined(TOOLKIT_VIEWS) - const GdkColor background_color = gfx::SkColorToGdkColor( - LocationBarView::GetColor(ToolbarModel::NONE, - LocationBarView::BACKGROUND)); - background_color_ptr = &background_color; -#else - background_color_ptr = &LocationBarViewGtk::kBackgroundColor; -#endif - gtk_widget_modify_cursor( - text_view_, >k_util::kGdkBlack, >k_util::kGdkGray); - gtk_widget_modify_base(text_view_, GTK_STATE_NORMAL, background_color_ptr); - -#if !defined(TOOLKIT_VIEWS) - GdkColor c; - // Override the selected colors so we don't leak colors from the current - // gtk theme into the chrome-theme. - c = gfx::SkColorToGdkColor( - theme_service_->get_active_selection_bg_color()); - gtk_widget_modify_base(text_view_, GTK_STATE_SELECTED, &c); - - c = gfx::SkColorToGdkColor( - theme_service_->get_active_selection_fg_color()); - gtk_widget_modify_text(text_view_, GTK_STATE_SELECTED, &c); - - c = gfx::SkColorToGdkColor( - theme_service_->get_inactive_selection_bg_color()); - gtk_widget_modify_base(text_view_, GTK_STATE_ACTIVE, &c); - - c = gfx::SkColorToGdkColor( - theme_service_->get_inactive_selection_fg_color()); - gtk_widget_modify_text(text_view_, GTK_STATE_ACTIVE, &c); -#endif - - // Until we switch to vector graphics, force the font size. - gtk_util::ForceFontSizePixels(text_view_, GetFont().GetFontSize()); - gtk_util::ForceFontSizePixels(instant_view_, GetFont().GetFontSize()); - - g_object_set(faded_text_tag_, "foreground", kTextBaseColor, NULL); - g_object_set(normal_text_tag_, "foreground", "#000000", NULL); - } - - AdjustVerticalAlignmentOfInstantView(); - UpdateInstantViewColors(); -} - -void AutocompleteEditViewGtk::UpdateInstantViewColors() { - SkColor selection_text, selection_bg; - GdkColor faded_text, normal_bg; - -#if defined(TOOLKIT_VIEWS) - bool use_gtk = false; -#else - bool use_gtk = theme_service_->UseGtkTheme(); -#endif - - if (use_gtk) { - GtkStyle* style = gtk_rc_get_style(instant_view_); - - faded_text = gtk_util::AverageColors( - style->text[GTK_STATE_NORMAL], style->base[GTK_STATE_NORMAL]); - normal_bg = style->base[GTK_STATE_NORMAL]; - - selection_text = gfx::GdkColorToSkColor(style->text[GTK_STATE_SELECTED]); - selection_bg = gfx::GdkColorToSkColor(style->base[GTK_STATE_SELECTED]); - } else { - gdk_color_parse(kTextBaseColor, &faded_text); - -#if defined(TOOLKIT_VIEWS) - normal_bg = gfx::SkColorToGdkColor( - LocationBarView::GetColor(ToolbarModel::NONE, - LocationBarView::BACKGROUND)); - selection_text = LocationBarView::GetColor( - ToolbarModel::NONE, LocationBarView::SELECTED_TEXT); - - GtkStyle* style = gtk_rc_get_style(instant_view_); - selection_bg = gfx::GdkColorToSkColor(style->base[GTK_STATE_SELECTED]); -#else - normal_bg = LocationBarViewGtk::kBackgroundColor; - selection_text = - theme_service_->get_active_selection_fg_color(); - selection_bg = - theme_service_->get_active_selection_bg_color(); -#endif - } - - double alpha = instant_animation_->is_animating() ? - instant_animation_->GetCurrentValue() : 0.0; - GdkColor text = gfx::SkColorToGdkColor(color_utils::AlphaBlend( - selection_text, - gfx::GdkColorToSkColor(faded_text), - alpha * 0xff)); - GdkColor bg = gfx::SkColorToGdkColor(color_utils::AlphaBlend( - selection_bg, - gfx::GdkColorToSkColor(normal_bg), - alpha * 0xff)); - - if (alpha > 0.0) { - gtk_label_select_region(GTK_LABEL(instant_view_), 0, -1); - // ACTIVE is the state for text that is selected, but not focused. - gtk_widget_modify_text(instant_view_, GTK_STATE_ACTIVE, &text); - gtk_widget_modify_base(instant_view_, GTK_STATE_ACTIVE, &bg); - } else { - // When the text is unselected, fg is used for text color, the state - // is NORMAL, and the background is transparent. - gtk_widget_modify_fg(instant_view_, GTK_STATE_NORMAL, &text); - } -} - -void AutocompleteEditViewGtk::HandleBeginUserAction(GtkTextBuffer* sender) { - OnBeforePossibleChange(); -} - -void AutocompleteEditViewGtk::HandleEndUserAction(GtkTextBuffer* sender) { - OnAfterPossibleChange(); -} - -gboolean AutocompleteEditViewGtk::HandleKeyPress(GtkWidget* widget, - GdkEventKey* event) { - // Background of this piece of complicated code: - // The omnibox supports several special behaviors which may be triggered by - // certain key events: - // Tab to search - triggered by Tab key - // Accept input - triggered by Enter key - // Revert input - triggered by Escape key - // - // Because we use a GtkTextView object |text_view_| for text input, we need - // send all key events to |text_view_| before handling them, to make sure - // IME works without any problem. So here, we intercept "key-press-event" - // signal of |text_view_| object and call its default handler to handle the - // key event first. - // - // Then if the key event is one of Tab, Enter and Escape, we need to trigger - // the corresponding special behavior if IME did not handle it. - // For Escape key, if the default signal handler returns FALSE, then we know - // it's not handled by IME. - // - // For Tab key, as "accepts-tab" property of |text_view_| is set to FALSE, - // if IME did not handle it then "move-focus" signal will be emitted by the - // default signal handler of |text_view_|. So we can intercept "move-focus" - // signal of |text_view_| to know if a Tab key press event was handled by IME, - // and trigger Tab to search behavior when necessary in the signal handler. - // - // But for Enter key, if IME did not handle the key event, the default signal - // handler will delete current selection range and insert '\n' and always - // return TRUE. We need to prevent |text_view_| from performing this default - // action if IME did not handle the key event, because we don't want the - // content of omnibox to be changed before triggering our special behavior. - // Otherwise our special behavior would not be performed correctly. - // - // But there is no way for us to prevent GtkTextView from handling the key - // event and performing built-in operation. So in order to achieve our goal, - // "insert-text" signal of |text_buffer_| object is intercepted, and - // following actions are done in the signal handler: - // - If there is only one character in inserted text, and it's '\n' or '\r', - // then set |enter_was_inserted_| to true. - // - Filter out all new line and tab characters. - // - // So if |enter_was_inserted_| is true after calling |text_view_|'s default - // signal handler against an Enter key press event, then we know that the - // Enter key press event was handled by GtkTextView rather than IME, and can - // perform the special behavior for Enter key safely. - // - // Now the last thing is to prevent the content of omnibox from being changed - // by GtkTextView when Enter key is pressed. As OnBeforePossibleChange() and - // OnAfterPossibleChange() will be called by GtkTextView before and after - // changing the content, and the content is already saved in - // OnBeforePossibleChange(), so if the Enter key press event was not handled - // by IME, it's easy to restore the content in OnAfterPossibleChange(), as if - // it's not changed at all. - - GtkWidgetClass* klass = GTK_WIDGET_GET_CLASS(widget); - - enter_was_pressed_ = event->keyval == GDK_Return || - event->keyval == GDK_ISO_Enter || - event->keyval == GDK_KP_Enter; - - // Set |tab_was_pressed_| to true if it's a Tab key press event, so that our - // handler of "move-focus" signal can trigger Tab to search behavior when - // necessary. - tab_was_pressed_ = (event->keyval == GDK_Tab || - event->keyval == GDK_ISO_Left_Tab || - event->keyval == GDK_KP_Tab) && - !(event->state & (GDK_CONTROL_MASK | GDK_SHIFT_MASK)); - - delete_was_pressed_ = event->keyval == GDK_Delete || - event->keyval == GDK_KP_Delete; - - // Reset |enter_was_inserted_|, which may be set in the "insert-text" signal - // handler, so that we'll know if an Enter key event was handled by IME. - enter_was_inserted_ = false; - - // Reset |paste_clipboard_requested_| to make sure we won't misinterpret this - // key input action as a paste action. - paste_clipboard_requested_ = false; - - // Reset |text_changed_| before passing the key event on to the text view. - text_changed_ = false; - - OnBeforePossibleChange(); - handling_key_press_ = true; - content_maybe_changed_by_key_press_ = false; - - // Call the default handler, so that IME can work as normal. - // New line characters will be filtered out by our "insert-text" - // signal handler attached to |text_buffer_| object. - gboolean result = klass->key_press_event(widget, event); - - handling_key_press_ = false; - if (content_maybe_changed_by_key_press_) - OnAfterPossibleChange(); - - // Set |tab_was_pressed_| to false, to make sure Tab to search behavior can - // only be triggered by pressing Tab key. - tab_was_pressed_ = false; - - if (enter_was_pressed_ && enter_was_inserted_) { - bool alt_held = (event->state & GDK_MOD1_MASK); - model_->AcceptInput(alt_held ? NEW_FOREGROUND_TAB : CURRENT_TAB, false); - result = TRUE; - } else if (!result && event->keyval == GDK_Escape && - (event->state & gtk_accelerator_get_default_mod_mask()) == 0) { - // We can handle the Escape key if |text_view_| did not handle it. - // If it's not handled by us, then we need to propagate it up to the parent - // widgets, so that Escape accelerator can still work. - result = model_->OnEscapeKeyPressed(); - } else if (event->keyval == GDK_Control_L || event->keyval == GDK_Control_R) { - // Omnibox2 can switch its contents while pressing a control key. To switch - // the contents of omnibox2, we notify the AutocompleteEditModel class when - // the control-key state is changed. - model_->OnControlKeyChanged(true); - } else if (!text_changed_ && event->keyval == GDK_Delete && - event->state & GDK_SHIFT_MASK) { - // If shift+del didn't change the text, we let this delete an entry from - // the popup. We can't check to see if the IME handled it because even if - // nothing is selected, the IME or the TextView still report handling it. - if (model_->popup_model()->IsOpen()) - model_->popup_model()->TryDeletingCurrentItem(); - } - - // Set |enter_was_pressed_| to false, to make sure OnAfterPossibleChange() can - // act as normal for changes made by other events. - enter_was_pressed_ = false; - - // If the key event is not handled by |text_view_| or us, then we need to - // propagate the key event up to parent widgets by returning FALSE. - // In this case we need to stop the signal emission explicitly to prevent the - // default "key-press-event" handler of |text_view_| from being called again. - if (!result) { - static guint signal_id = - g_signal_lookup("key-press-event", GTK_TYPE_WIDGET); - g_signal_stop_emission(widget, signal_id, 0); - } - -#if defined(TOOLKIT_VIEWS) - location_bar_view_->GetWidget()->NotifyAccessibilityEvent( - location_bar_view_, ui::AccessibilityTypes::EVENT_TEXT_CHANGED, true); -#endif - - return result; -} - -gboolean AutocompleteEditViewGtk::HandleKeyRelease(GtkWidget* widget, - GdkEventKey* event) { - // Omnibox2 can switch its contents while pressing a control key. To switch - // the contents of omnibox2, we notify the AutocompleteEditModel class when - // the control-key state is changed. - if (event->keyval == GDK_Control_L || event->keyval == GDK_Control_R) { - // Round trip to query the control state after the release. This allows - // you to release one control key while still holding another control key. - GdkDisplay* display = gdk_drawable_get_display(event->window); - GdkModifierType mod; - gdk_display_get_pointer(display, NULL, NULL, NULL, &mod); - if (!(mod & GDK_CONTROL_MASK)) - model_->OnControlKeyChanged(false); - } - - // Even though we handled the press ourselves, let GtkTextView handle the - // release. It shouldn't do anything particularly interesting, but it will - // handle the IME work for us. - return FALSE; // Propagate into GtkTextView. -} - -gboolean AutocompleteEditViewGtk::HandleViewButtonPress(GtkWidget* sender, - GdkEventButton* event) { - // We don't need to care about double and triple clicks. - if (event->type != GDK_BUTTON_PRESS) - return FALSE; - - DCHECK(text_view_); - - if (event->button == 1) { -#if defined(OS_CHROMEOS) - // When the first button is pressed, track some stuff that will help us - // determine whether we should select all of the text when the button is - // released. - button_1_pressed_ = true; - text_view_focused_before_button_press_ = GTK_WIDGET_HAS_FOCUS(text_view_); - text_selected_during_click_ = false; -#endif - - // Button press event may change the selection, we need to record the change - // and report it to |model_| later when button is released. - OnBeforePossibleChange(); - } else if (event->button == 2) { - // GtkTextView pastes PRIMARY selection with middle click. - // We can't call model_->on_paste_replacing_all() here, because the actual - // paste clipboard action may not be performed if the clipboard is empty. - paste_clipboard_requested_ = true; - } - return FALSE; -} - -gboolean AutocompleteEditViewGtk::HandleViewButtonRelease( - GtkWidget* sender, GdkEventButton* event) { - if (event->button != 1) - return FALSE; - - DCHECK(text_view_); - -#if defined(OS_CHROMEOS) - button_1_pressed_ = false; -#endif - - // Call the GtkTextView default handler, ignoring the fact that it will - // likely have told us to stop propagating. We want to handle selection. - GtkWidgetClass* klass = GTK_WIDGET_GET_CLASS(text_view_); - klass->button_release_event(text_view_, event); - -#if defined(OS_CHROMEOS) - if (!text_view_focused_before_button_press_ && !text_selected_during_click_) { - // If this was a focusing click and the user didn't drag to highlight any - // text, select the full input and update the PRIMARY selection. - SelectAllInternal(false, true); - - // So we told the buffer where the cursor should be, but make sure to tell - // the view so it can scroll it to be visible if needed. - // NOTE: This function doesn't seem to like a count of 0, looking at the - // code it will skip an important loop. Use -1 to achieve the same. - GtkTextIter start, end; - GetTextBufferBounds(&start, &end); - gtk_text_view_move_visually(GTK_TEXT_VIEW(text_view_), &start, -1); - } -#endif - - // Inform |model_| about possible text selection change. - OnAfterPossibleChange(); - - return TRUE; // Don't continue, we called the default handler already. -} - -gboolean AutocompleteEditViewGtk::HandleViewFocusIn(GtkWidget* sender, - GdkEventFocus* event) { - DCHECK(text_view_); - update_popup_without_focus_ = false; - - GdkModifierType modifiers; - gdk_window_get_pointer(text_view_->window, NULL, NULL, &modifiers); - model_->OnSetFocus((modifiers & GDK_CONTROL_MASK) != 0); - controller_->OnSetFocus(); - // TODO(deanm): Some keyword hit business, etc here. - - g_signal_connect( - gdk_keymap_get_for_display(gtk_widget_get_display(text_view_)), - "direction-changed", - G_CALLBACK(&HandleKeymapDirectionChangedThunk), this); - - AdjustTextJustification(); - - return FALSE; // Continue propagation. -} - -gboolean AutocompleteEditViewGtk::HandleViewFocusOut(GtkWidget* sender, - GdkEventFocus* event) { - DCHECK(text_view_); - GtkWidget* view_getting_focus = NULL; - GtkWindow* toplevel = platform_util::GetTopLevel(sender); - if (gtk_window_is_active(toplevel)) - view_getting_focus = going_to_focus_; - - // This must be invoked before ClosePopup. - model_->OnWillKillFocus(view_getting_focus); - - // Close the popup. - ClosePopup(); - // Tell the model to reset itself. - model_->OnKillFocus(); - controller_->OnKillFocus(); - - g_signal_handlers_disconnect_by_func( - gdk_keymap_get_for_display(gtk_widget_get_display(text_view_)), - reinterpret_cast<gpointer>(&HandleKeymapDirectionChangedThunk), this); - - return FALSE; // Pass the event on to the GtkTextView. -} - -void AutocompleteEditViewGtk::HandleViewMoveCursor( - GtkWidget* sender, - GtkMovementStep step, - gint count, - gboolean extend_selection) { - DCHECK(text_view_); - GtkTextIter sel_start, sel_end; - gboolean has_selection = - gtk_text_buffer_get_selection_bounds(text_buffer_, &sel_start, &sel_end); - bool handled = false; - - if (step == GTK_MOVEMENT_VISUAL_POSITIONS && !extend_selection && - (count == 1 || count == -1)) { - // We need to take the content direction into account when handling cursor - // movement, because the behavior of Left and Right key will be inverted if - // the direction is RTL. Although we should check the direction around the - // input caret, it's much simpler and good enough to check whole content's - // direction. - PangoDirection content_dir = GetContentDirection(); - gint count_towards_end = content_dir == PANGO_DIRECTION_RTL ? -1 : 1; - - // We want the GtkEntry behavior when you move the cursor while you have a - // selection. GtkTextView just drops the selection and moves the cursor, - // but instead we want to move the cursor to the appropiate end of the - // selection. - if (has_selection) { - // We have a selection and start / end are in ascending order. - // Cursor placement will remove the selection, so we need inform - // |model_| about this change by - // calling On{Before|After}PossibleChange() methods. - OnBeforePossibleChange(); - gtk_text_buffer_place_cursor( - text_buffer_, count == count_towards_end ? &sel_end : &sel_start); - OnAfterPossibleChange(); - handled = true; - } else if (count == count_towards_end && !IsCaretAtEnd()) { - handled = model_->CommitSuggestedText(true); - } - } else if (step == GTK_MOVEMENT_PAGES) { // Page up and down. - // Multiply by count for the direction (if we move too much that's ok). - model_->OnUpOrDownKeyPressed(model_->result().size() * count); - handled = true; - } else if (step == GTK_MOVEMENT_DISPLAY_LINES) { // Arrow up and down. - model_->OnUpOrDownKeyPressed(count); - handled = true; - } - - if (!handled) { - // Cursor movement may change the selection, we need to record the change - // and report it to |model_|. - if (has_selection || extend_selection) - OnBeforePossibleChange(); - - // Propagate into GtkTextView - GtkTextViewClass* klass = GTK_TEXT_VIEW_GET_CLASS(text_view_); - klass->move_cursor(GTK_TEXT_VIEW(text_view_), step, count, - extend_selection); - - if (has_selection || extend_selection) - OnAfterPossibleChange(); - } - - // move-cursor doesn't use a signal accumulator on the return value (it - // just ignores then), so we have to stop the propagation. - static guint signal_id = g_signal_lookup("move-cursor", GTK_TYPE_TEXT_VIEW); - g_signal_stop_emission(text_view_, signal_id, 0); -} - -void AutocompleteEditViewGtk::HandleViewSizeRequest(GtkWidget* sender, - GtkRequisition* req) { - // Don't force a minimum width, but use the font-relative height. This is a - // run-first handler, so the default handler was already called. - req->width = 1; -} - -void AutocompleteEditViewGtk::HandlePopupMenuDeactivate(GtkWidget* sender) { - // When the context menu appears, |text_view_|'s focus is lost. After an item - // is activated, the focus comes back to |text_view_|, but only after the - // check in UpdatePopup(). We set this flag to make UpdatePopup() aware that - // it will be receiving focus again. - if (!model_->has_focus()) - update_popup_without_focus_ = true; -} - -void AutocompleteEditViewGtk::HandlePopulatePopup(GtkWidget* sender, - GtkMenu* menu) { - GtkWidget* separator = gtk_separator_menu_item_new(); - gtk_menu_shell_append(GTK_MENU_SHELL(menu), separator); - gtk_widget_show(separator); - - // Search Engine menu item. - GtkWidget* search_engine_menuitem = gtk_menu_item_new_with_mnemonic( - gfx::ConvertAcceleratorsFromWindowsStyle( - l10n_util::GetStringUTF8(IDS_EDIT_SEARCH_ENGINES)).c_str()); - gtk_menu_shell_append(GTK_MENU_SHELL(menu), search_engine_menuitem); - g_signal_connect(search_engine_menuitem, "activate", - G_CALLBACK(HandleEditSearchEnginesThunk), this); - gtk_widget_set_sensitive(search_engine_menuitem, - command_updater_->IsCommandEnabled(IDC_EDIT_SEARCH_ENGINES)); - gtk_widget_show(search_engine_menuitem); - - // We need to update the paste and go controller before we know what text - // to show. We could do this all asynchronously, but it would be elaborate - // because we'd have to account for multiple menus showing, getting called - // back after shutdown, and similar issues. - GtkClipboard* x_clipboard = gtk_clipboard_get(GDK_SELECTION_CLIPBOARD); - gchar* text = gtk_clipboard_wait_for_text(x_clipboard); - string16 text_wstr = UTF8ToUTF16(text ? text : ""); - g_free(text); - - // Paste and Go menu item. - GtkWidget* paste_go_menuitem = gtk_menu_item_new_with_mnemonic( - gfx::ConvertAcceleratorsFromWindowsStyle( - l10n_util::GetStringUTF8(model_->is_paste_and_search() ? - IDS_PASTE_AND_SEARCH : IDS_PASTE_AND_GO)).c_str()); - gtk_menu_shell_append(GTK_MENU_SHELL(menu), paste_go_menuitem); - g_signal_connect(paste_go_menuitem, "activate", - G_CALLBACK(HandlePasteAndGoThunk), this); - gtk_widget_set_sensitive(paste_go_menuitem, - model_->CanPasteAndGo(text_wstr)); - gtk_widget_show(paste_go_menuitem); - - g_signal_connect(menu, "deactivate", - G_CALLBACK(HandlePopupMenuDeactivateThunk), this); -} - -void AutocompleteEditViewGtk::HandleEditSearchEngines(GtkWidget* sender) { - command_updater_->ExecuteCommand(IDC_EDIT_SEARCH_ENGINES); -} - -void AutocompleteEditViewGtk::HandlePasteAndGo(GtkWidget* sender) { - model_->PasteAndGo(); -} - -void AutocompleteEditViewGtk::HandleMarkSet(GtkTextBuffer* buffer, - GtkTextIter* location, - GtkTextMark* mark) { - if (!text_buffer_ || buffer != text_buffer_) - return; - - if (mark != gtk_text_buffer_get_insert(text_buffer_) && - mark != gtk_text_buffer_get_selection_bound(text_buffer_)) { - return; - } - - StopAnimation(); - - // If we are here, that means the user may be changing the selection - selection_suggested_ = false; - - // Get the currently-selected text, if there is any. - std::string new_selected_text = GetSelectedText(); - -#if defined(OS_CHROMEOS) - // If the user just selected some text with the mouse (or at least while the - // mouse button was down), make sure that we won't blow their selection away - // later by selecting all of the text when the button is released. - if (button_1_pressed_ && !new_selected_text.empty()) - text_selected_during_click_ = true; -#endif - - // If we had some text selected earlier but it's no longer highlighted, we - // might need to save it now... - if (!selected_text_.empty() && new_selected_text.empty()) { - // ... but only if we currently own the selection. We want to manually - // update the selection when the text is unhighlighted because the user - // clicked in a blank area of the text view, but not when it's unhighlighted - // because another client or widget took the selection. (This handler gets - // called before the default handler, so as long as nobody else took the - // selection, the text buffer still owns it even if GTK is about to take it - // away in the default handler.) - GtkClipboard* clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY); - if (gtk_clipboard_get_owner(clipboard) == G_OBJECT(text_buffer_)) - SavePrimarySelection(selected_text_); - } - - selected_text_ = new_selected_text; -} - -// Override the primary selection the text buffer has set. This has to happen -// after the default handler for the "mark-set" signal. -void AutocompleteEditViewGtk::HandleMarkSetAfter(GtkTextBuffer* buffer, - GtkTextIter* location, - GtkTextMark* mark) { - if (!text_buffer_ || buffer != text_buffer_) - return; - - // We should only update primary selection when the user changes the selection - // range. - if (mark != gtk_text_buffer_get_insert(text_buffer_) && - mark != gtk_text_buffer_get_selection_bound(text_buffer_)) { - return; - } - - UpdatePrimarySelectionIfValidURL(); -} - -// Just use the default behavior for DnD, except if the drop can be a PasteAndGo -// then override. -void AutocompleteEditViewGtk::HandleDragDataReceived( - GtkWidget* sender, GdkDragContext* context, gint x, gint y, - GtkSelectionData* selection_data, guint target_type, guint time) { - DCHECK(text_view_); - - // Reset |paste_clipboard_requested_| to make sure we won't misinterpret this - // drop action as a paste action. - paste_clipboard_requested_ = false; - - // Don't try to PasteAndGo on drops originating from this omnibox. However, do - // allow default behavior for such drags. - if (context->source_window == text_view_->window) - return; - - guchar* text = gtk_selection_data_get_text(selection_data); - if (!text) - return; - - string16 possible_url = UTF8ToUTF16(reinterpret_cast<char*>(text)); - g_free(text); - if (OnPerformDropImpl(possible_url)) { - gtk_drag_finish(context, TRUE, TRUE, time); - - static guint signal_id = - g_signal_lookup("drag-data-received", GTK_TYPE_WIDGET); - g_signal_stop_emission(text_view_, signal_id, 0); - } -} - -void AutocompleteEditViewGtk::HandleDragDataGet( - GtkWidget* widget, - GdkDragContext* context, - GtkSelectionData* selection_data, - guint target_type, - guint time) { - DCHECK(text_view_); - // If GTK put the normal textual version of the selection in our drag data, - // put our doctored selection that might have the 'http://' prefix. Also, GTK - // is confused about signedness of its datatypes, leading to the weird switch - // statement (no set of casts fixes this). - switch (target_type) { - case GTK_TEXT_BUFFER_TARGET_INFO_TEXT: { - gtk_selection_data_set_text(selection_data, selected_text_.c_str(), -1); - } - } -} - -void AutocompleteEditViewGtk::HandleInsertText( - GtkTextBuffer* buffer, GtkTextIter* location, const gchar* text, gint len) { - std::string filtered_text; - filtered_text.reserve(len); - - // Filter out new line and tab characters. - // |text| is guaranteed to be a valid UTF-8 string, so we don't need to - // validate it here. - // - // If there was only a single character, then it might be generated by a key - // event. In this case, we save the single character to help our - // "key-press-event" signal handler distinguish if an Enter key event is - // handled by IME or not. - if (len == 1 && (text[0] == '\n' || text[0] == '\r')) - enter_was_inserted_ = true; - - const gchar* p = text; - while (*p && (p - text) < len) { - gunichar c = g_utf8_get_char(p); - const gchar* next = g_utf8_next_char(p); - - // 0x200B is Zero Width Space, which is inserted just before the instant - // anchor for working around the GtkTextView's misalignment bug. - // This character might be captured and inserted into the content by undo - // manager, so we need to filter it out here. - if (c != L'\n' && c != L'\r' && c != L'\t' && c != 0x200B) - filtered_text.append(p, next); - - p = next; - } - - if (filtered_text.length()) { - // Avoid inserting the text after the instant anchor. - ValidateTextBufferIter(location); - - // Call the default handler to insert filtered text. - GtkTextBufferClass* klass = GTK_TEXT_BUFFER_GET_CLASS(buffer); - klass->insert_text(buffer, location, filtered_text.data(), - static_cast<gint>(filtered_text.length())); - } - - // Stop propagating the signal emission to prevent the default handler from - // being called again. - static guint signal_id = g_signal_lookup("insert-text", GTK_TYPE_TEXT_BUFFER); - g_signal_stop_emission(buffer, signal_id, 0); -} - -void AutocompleteEditViewGtk::HandleBackSpace(GtkWidget* sender) { - // Checks if it's currently in keyword search mode. - if (model_->is_keyword_hint() || model_->keyword().empty()) - return; // Propgate into GtkTextView. - - DCHECK(text_view_); - - GtkTextIter sel_start, sel_end; - // Checks if there is some text selected. - if (gtk_text_buffer_get_selection_bounds(text_buffer_, &sel_start, &sel_end)) - return; // Propgate into GtkTextView. - - GtkTextIter start; - gtk_text_buffer_get_start_iter(text_buffer_, &start); - - if (!gtk_text_iter_equal(&start, &sel_start)) - return; // Propgate into GtkTextView. - - // We're showing a keyword and the user pressed backspace at the beginning - // of the text. Delete the selected keyword. - model_->ClearKeyword(GetText()); - - // Stop propagating the signal emission into GtkTextView. - static guint signal_id = g_signal_lookup("backspace", GTK_TYPE_TEXT_VIEW); - g_signal_stop_emission(text_view_, signal_id, 0); -} - -void AutocompleteEditViewGtk::HandleViewMoveFocus(GtkWidget* widget, - GtkDirectionType direction) { - if (!tab_was_pressed_) - return; - - // If special behavior is triggered, then stop the signal emission to - // prevent the focus from being moved. - bool handled = false; - - // Trigger Tab to search behavior only when Tab key is pressed. - if (model_->is_keyword_hint()) - handled = model_->AcceptKeyword(); - -#if GTK_CHECK_VERSION(2, 20, 0) - if (!handled && !preedit_.empty()) - handled = true; -#endif - - if (!handled && GTK_WIDGET_VISIBLE(instant_view_)) - handled = model_->CommitSuggestedText(true); - - if (!handled) { - if (!IsCaretAtEnd()) { - OnBeforePossibleChange(); - PlaceCaretAt(GetTextLength()); - OnAfterPossibleChange(); - handled = true; - } - } - - if (!handled) - handled = model_->AcceptCurrentInstantPreview(); - - if (handled) { - static guint signal_id = g_signal_lookup("move-focus", GTK_TYPE_WIDGET); - g_signal_stop_emission(widget, signal_id, 0); - } -} - -void AutocompleteEditViewGtk::HandleCopyClipboard(GtkWidget* sender) { - HandleCopyOrCutClipboard(true); -} - -void AutocompleteEditViewGtk::HandleCutClipboard(GtkWidget* sender) { - HandleCopyOrCutClipboard(false); -} - -void AutocompleteEditViewGtk::HandleCopyOrCutClipboard(bool copy) { - DCHECK(text_view_); - - // On copy or cut, we manually update the PRIMARY selection to contain the - // highlighted text. This matches Firefox -- we highlight the URL but don't - // update PRIMARY on Ctrl-L, so Ctrl-L, Ctrl-C and then middle-click is a - // convenient way to paste the current URL somewhere. - if (!gtk_text_buffer_get_has_selection(text_buffer_)) - return; - - GtkClipboard* clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY); - DCHECK(clipboard); - if (!clipboard) - return; - - CharRange selection = GetSelection(); - GURL url; - string16 text(UTF8ToUTF16(GetSelectedText())); - bool write_url; - model_->AdjustTextForCopy(selection.selection_min(), IsSelectAll(), &text, - &url, &write_url); - - if (write_url) { - BookmarkNodeData data; - data.ReadFromTuple(url, text); - data.WriteToClipboard(NULL); - - // Stop propagating the signal. - static guint copy_signal_id = - g_signal_lookup("copy-clipboard", GTK_TYPE_TEXT_VIEW); - static guint cut_signal_id = - g_signal_lookup("cut-clipboard", GTK_TYPE_TEXT_VIEW); - g_signal_stop_emission(text_view_, - copy ? copy_signal_id : cut_signal_id, - 0); - - if (!copy && gtk_text_view_get_editable(GTK_TEXT_VIEW(text_view_))) - gtk_text_buffer_delete_selection(text_buffer_, true, true); - } - - OwnPrimarySelection(UTF16ToUTF8(text)); -} - -bool AutocompleteEditViewGtk::OnPerformDropImpl(const string16& text) { - if (model_->CanPasteAndGo(CollapseWhitespace(text, true))) { - model_->PasteAndGo(); - return true; - } - - return false; -} - -gfx::Font AutocompleteEditViewGtk::GetFont() { -#if defined(TOOLKIT_VIEWS) - bool use_gtk = false; -#else - bool use_gtk = theme_service_->UseGtkTheme(); -#endif - - if (use_gtk) { - // If we haven't initialized the text view yet, just create a temporary one - // whose style we can grab. - GtkWidget* widget = text_view_ ? text_view_ : gtk_text_view_new(); - GtkRcStyle* rc_style = gtk_widget_get_modifier_style(widget); - gfx::Font font((rc_style && rc_style->font_desc) ? - rc_style->font_desc : - widget->style->font_desc); - if (!text_view_) - g_object_unref(g_object_ref_sink(widget)); - - // Scaling the font down for popup windows doesn't help here, since we just - // use the normal unforced font size when using the GTK theme. - return font; - } else { - return gfx::Font( - ResourceBundle::GetSharedInstance().GetFont(ResourceBundle::BaseFont). - GetFontName(), - popup_window_mode_ ? - browser_defaults::kAutocompleteEditFontPixelSizeInPopup : - browser_defaults::kAutocompleteEditFontPixelSize); - } -} - -void AutocompleteEditViewGtk::OwnPrimarySelection(const std::string& text) { - primary_selection_text_ = text; - - GtkTargetList* list = gtk_target_list_new(NULL, 0); - gtk_target_list_add_text_targets(list, 0); - gint len; - GtkTargetEntry* entries = gtk_target_table_new_from_list(list, &len); - - // When |text_buffer_| is destroyed, it will clear the clipboard, hence - // we needn't worry about calling gtk_clipboard_clear(). - gtk_clipboard_set_with_owner(gtk_clipboard_get(GDK_SELECTION_PRIMARY), - entries, len, - ClipboardGetSelectionThunk, - ClipboardSelectionCleared, - G_OBJECT(text_buffer_)); - - gtk_target_list_unref(list); - gtk_target_table_free(entries, len); -} - -void AutocompleteEditViewGtk::HandlePasteClipboard(GtkWidget* sender) { - // We can't call model_->on_paste_replacing_all() here, because the actual - // paste clipboard action may not be performed if the clipboard is empty. - paste_clipboard_requested_ = true; -} - -gfx::Rect AutocompleteEditViewGtk::WindowBoundsFromIters( - GtkTextIter* iter1, GtkTextIter* iter2) { - GdkRectangle start_location, end_location; - GtkTextView* text_view = GTK_TEXT_VIEW(text_view_); - gtk_text_view_get_iter_location(text_view, iter1, &start_location); - gtk_text_view_get_iter_location(text_view, iter2, &end_location); - - gint x1, x2, y1, y2; - gtk_text_view_buffer_to_window_coords(text_view, GTK_TEXT_WINDOW_WIDGET, - start_location.x, start_location.y, - &x1, &y1); - gtk_text_view_buffer_to_window_coords(text_view, GTK_TEXT_WINDOW_WIDGET, - end_location.x + end_location.width, - end_location.y + end_location.height, - &x2, &y2); - - return gfx::Rect(x1, y1, x2 - x1, y2 - y1); -} - -gboolean AutocompleteEditViewGtk::HandleExposeEvent(GtkWidget* sender, - GdkEventExpose* expose) { - if (strikethrough_.cp_min >= strikethrough_.cp_max) - return FALSE; - DCHECK(text_view_); - - gfx::Rect expose_rect(expose->area); - - GtkTextIter iter_min, iter_max; - ItersFromCharRange(strikethrough_, &iter_min, &iter_max); - gfx::Rect strikethrough_rect = WindowBoundsFromIters(&iter_min, &iter_max); - - if (!expose_rect.Intersects(strikethrough_rect)) - return FALSE; - - // Finally, draw. - cairo_t* cr = gdk_cairo_create(GDK_DRAWABLE(expose->window)); - cairo_rectangle(cr, expose_rect.x(), expose_rect.y(), - expose_rect.width(), expose_rect.height()); - cairo_clip(cr); - - // TODO(estade): we probably shouldn't draw the strikethrough on selected - // text. I started to do this, but it was way more effort than it seemed - // worth. - strikethrough_rect.Inset(kStrikethroughStrokeWidth, - kStrikethroughStrokeWidth); - cairo_set_source_rgb(cr, kStrikethroughStrokeRed, 0.0, 0.0); - cairo_set_line_width(cr, kStrikethroughStrokeWidth); - cairo_set_line_cap(cr, CAIRO_LINE_CAP_ROUND); - cairo_move_to(cr, strikethrough_rect.x(), strikethrough_rect.bottom()); - cairo_line_to(cr, strikethrough_rect.right(), strikethrough_rect.y()); - cairo_stroke(cr); - cairo_destroy(cr); - - return FALSE; -} - -void AutocompleteEditViewGtk::SelectAllInternal(bool reversed, - bool update_primary_selection) { - GtkTextIter start, end; - if (reversed) { - GetTextBufferBounds(&end, &start); - } else { - GetTextBufferBounds(&start, &end); - } - if (!update_primary_selection) - StartUpdatingHighlightedText(); - gtk_text_buffer_select_range(text_buffer_, &start, &end); - if (!update_primary_selection) - FinishUpdatingHighlightedText(); -} - -void AutocompleteEditViewGtk::StartUpdatingHighlightedText() { - if (GTK_WIDGET_REALIZED(text_view_)) { - GtkClipboard* clipboard = - gtk_widget_get_clipboard(text_view_, GDK_SELECTION_PRIMARY); - DCHECK(clipboard); - if (clipboard) - gtk_text_buffer_remove_selection_clipboard(text_buffer_, clipboard); - } - g_signal_handler_block(text_buffer_, mark_set_handler_id_); - g_signal_handler_block(text_buffer_, mark_set_handler_id2_); -} - -void AutocompleteEditViewGtk::FinishUpdatingHighlightedText() { - if (GTK_WIDGET_REALIZED(text_view_)) { - GtkClipboard* clipboard = - gtk_widget_get_clipboard(text_view_, GDK_SELECTION_PRIMARY); - DCHECK(clipboard); - if (clipboard) - gtk_text_buffer_add_selection_clipboard(text_buffer_, clipboard); - } - g_signal_handler_unblock(text_buffer_, mark_set_handler_id_); - g_signal_handler_unblock(text_buffer_, mark_set_handler_id2_); -} - -AutocompleteEditViewGtk::CharRange - AutocompleteEditViewGtk::GetSelection() const { - // You can not just use get_selection_bounds here, since the order will be - // ascending, and you don't know where the user's start and end of the - // selection was (if the selection was forwards or backwards). Get the - // actual marks so that we can preserve the selection direction. - GtkTextIter start, insert; - GtkTextMark* mark; - - mark = gtk_text_buffer_get_selection_bound(text_buffer_); - gtk_text_buffer_get_iter_at_mark(text_buffer_, &start, mark); - - mark = gtk_text_buffer_get_insert(text_buffer_); - gtk_text_buffer_get_iter_at_mark(text_buffer_, &insert, mark); - - gint start_offset = gtk_text_iter_get_offset(&start); - gint end_offset = gtk_text_iter_get_offset(&insert); - -#if GTK_CHECK_VERSION(2, 20, 0) - // Nothing should be selected when we are in the middle of composition. - DCHECK(preedit_.empty() || start_offset == end_offset); - if (!preedit_.empty()) { - start_offset += preedit_.size(); - end_offset += preedit_.size(); - } -#endif - - return CharRange(start_offset, end_offset); -} - -void AutocompleteEditViewGtk::ItersFromCharRange(const CharRange& range, - GtkTextIter* iter_min, - GtkTextIter* iter_max) { - DCHECK(!IsImeComposing()); - gtk_text_buffer_get_iter_at_offset(text_buffer_, iter_min, range.cp_min); - gtk_text_buffer_get_iter_at_offset(text_buffer_, iter_max, range.cp_max); -} - -int AutocompleteEditViewGtk::GetTextLength() const { - GtkTextIter end; - gtk_text_buffer_get_iter_at_mark(text_buffer_, &end, instant_mark_); -#if GTK_CHECK_VERSION(2, 20, 0) - // We need to count the length of the text being composed, because we treat - // it as part of the content in GetText(). - return gtk_text_iter_get_offset(&end) + preedit_.size(); -#else - return gtk_text_iter_get_offset(&end); -#endif -} - -void AutocompleteEditViewGtk::PlaceCaretAt(int pos) { - GtkTextIter cursor; - gtk_text_buffer_get_iter_at_offset(text_buffer_, &cursor, pos); - gtk_text_buffer_place_cursor(text_buffer_, &cursor); -} - -bool AutocompleteEditViewGtk::IsCaretAtEnd() const { - const CharRange selection = GetSelection(); - return selection.cp_min == selection.cp_max && - selection.cp_min == GetTextLength(); -} - -void AutocompleteEditViewGtk::EmphasizeURLComponents() { -#if GTK_CHECK_VERSION(2, 20, 0) - // We can't change the text style easily, if the preedit string (the text - // being composed by the input method) is not empty, which is not treated as - // a part of the text content inside GtkTextView. And it's ok to simply return - // in this case, as this method will be called again when the preedit string - // gets committed. - if (preedit_.size()) { - strikethrough_ = CharRange(); - return; - } -#endif - // See whether the contents are a URL with a non-empty host portion, which we - // should emphasize. To check for a URL, rather than using the type returned - // by Parse(), ask the model, which will check the desired page transition for - // this input. This can tell us whether an UNKNOWN input string is going to - // be treated as a search or a navigation, and is the same method the Paste - // And Go system uses. - url_parse::Component scheme, host; - string16 text(GetText()); - AutocompleteInput::ParseForEmphasizeComponents( - text, model_->GetDesiredTLD(), &scheme, &host); - const bool emphasize = model_->CurrentTextIsURL() && (host.len > 0); - - // Set the baseline emphasis. - GtkTextIter start, end; - GetTextBufferBounds(&start, &end); - gtk_text_buffer_remove_all_tags(text_buffer_, &start, &end); - if (emphasize) { - gtk_text_buffer_apply_tag(text_buffer_, faded_text_tag_, &start, &end); - - // We've found a host name, give it more emphasis. - gtk_text_buffer_get_iter_at_line_index(text_buffer_, &start, 0, - GetUTF8Offset(text, - host.begin)); - gtk_text_buffer_get_iter_at_line_index(text_buffer_, &end, 0, - GetUTF8Offset(text, - host.end())); - - gtk_text_buffer_apply_tag(text_buffer_, normal_text_tag_, &start, &end); - } else { - gtk_text_buffer_apply_tag(text_buffer_, normal_text_tag_, &start, &end); - } - - strikethrough_ = CharRange(); - // Emphasize the scheme for security UI display purposes (if necessary). - if (!model_->user_input_in_progress() && scheme.is_nonempty() && - (security_level_ != ToolbarModel::NONE)) { - CharRange scheme_range = CharRange(GetUTF8Offset(text, scheme.begin), - GetUTF8Offset(text, scheme.end())); - ItersFromCharRange(scheme_range, &start, &end); - - if (security_level_ == ToolbarModel::SECURITY_ERROR) { - strikethrough_ = scheme_range; - // When we draw the strikethrough, we don't want to include the ':' at the - // end of the scheme. - strikethrough_.cp_max--; - - gtk_text_buffer_apply_tag(text_buffer_, security_error_scheme_tag_, - &start, &end); - } else if (security_level_ == ToolbarModel::SECURITY_WARNING) { - gtk_text_buffer_apply_tag(text_buffer_, faded_text_tag_, &start, &end); - } else { - gtk_text_buffer_apply_tag(text_buffer_, secure_scheme_tag_, &start, &end); - } - } -} - -void AutocompleteEditViewGtk::StopAnimation() { - // Clear the animation delegate so we don't get an AnimationEnded() callback. - instant_animation_->set_delegate(NULL); - instant_animation_->Stop(); - UpdateInstantViewColors(); -} - -void AutocompleteEditViewGtk::TextChanged() { - EmphasizeURLComponents(); - model_->OnChanged(); -} - -void AutocompleteEditViewGtk::SavePrimarySelection( - const std::string& selected_text) { - DCHECK(text_view_); - - GtkClipboard* clipboard = - gtk_widget_get_clipboard(text_view_, GDK_SELECTION_PRIMARY); - DCHECK(clipboard); - if (!clipboard) - return; - - gtk_clipboard_set_text( - clipboard, selected_text.data(), selected_text.size()); -} - -void AutocompleteEditViewGtk::SetTextAndSelectedRange(const string16& text, - const CharRange& range) { - if (text != GetText()) { - std::string utf8 = UTF16ToUTF8(text); - gtk_text_buffer_set_text(text_buffer_, utf8.data(), utf8.length()); - } - SetSelectedRange(range); - AdjustTextJustification(); -} - -void AutocompleteEditViewGtk::SetSelectedRange(const CharRange& range) { - GtkTextIter insert, bound; - ItersFromCharRange(range, &bound, &insert); - gtk_text_buffer_select_range(text_buffer_, &insert, &bound); - - // This should be set *after* setting the selection range, in case setting the - // selection triggers HandleMarkSet which sets |selection_suggested_| to - // false. - selection_suggested_ = true; -} - -void AutocompleteEditViewGtk::AdjustTextJustification() { - DCHECK(text_view_); - - PangoDirection content_dir = GetContentDirection(); - - // Use keymap direction if content does not have strong direction. - // It matches the behavior of GtkTextView. - if (content_dir == PANGO_DIRECTION_NEUTRAL) { - content_dir = gdk_keymap_get_direction( - gdk_keymap_get_for_display(gtk_widget_get_display(text_view_))); - } - - GtkTextDirection widget_dir = gtk_widget_get_direction(text_view_); - - if ((widget_dir == GTK_TEXT_DIR_RTL && content_dir == PANGO_DIRECTION_LTR) || - (widget_dir == GTK_TEXT_DIR_LTR && content_dir == PANGO_DIRECTION_RTL)) { - gtk_text_view_set_justification(GTK_TEXT_VIEW(text_view_), - GTK_JUSTIFY_RIGHT); - } else { - gtk_text_view_set_justification(GTK_TEXT_VIEW(text_view_), - GTK_JUSTIFY_LEFT); - } -} - -PangoDirection AutocompleteEditViewGtk::GetContentDirection() { - GtkTextIter iter; - gtk_text_buffer_get_start_iter(text_buffer_, &iter); - - PangoDirection dir = PANGO_DIRECTION_NEUTRAL; - do { - dir = pango_unichar_direction(gtk_text_iter_get_char(&iter)); - if (dir != PANGO_DIRECTION_NEUTRAL) - break; - } while (gtk_text_iter_forward_char(&iter)); - - return dir; -} - -void AutocompleteEditViewGtk::HandleWidgetDirectionChanged( - GtkWidget* sender, GtkTextDirection previous_direction) { - AdjustTextJustification(); -} - -void AutocompleteEditViewGtk::HandleDeleteFromCursor(GtkWidget *sender, - GtkDeleteType type, gint count) { - // If the selected text was suggested for autocompletion, then erase those - // first and then let the default handler take over. - if (selection_suggested_) { - gtk_text_buffer_delete_selection(text_buffer_, true, true); - selection_suggested_ = false; - } -} - -void AutocompleteEditViewGtk::HandleKeymapDirectionChanged(GdkKeymap* sender) { - AdjustTextJustification(); -} - -void AutocompleteEditViewGtk::HandleDeleteRange(GtkTextBuffer* buffer, - GtkTextIter* start, - GtkTextIter* end) { - // Prevent the user from deleting the instant anchor. We can't simply set the - // instant anchor readonly by applying a tag with "editable" = FALSE, because - // it'll prevent the insert caret from blinking. - ValidateTextBufferIter(start); - ValidateTextBufferIter(end); - if (!gtk_text_iter_compare(start, end)) { - static guint signal_id = - g_signal_lookup("delete-range", GTK_TYPE_TEXT_BUFFER); - g_signal_stop_emission(buffer, signal_id, 0); - } -} - -void AutocompleteEditViewGtk::HandleMarkSetAlways(GtkTextBuffer* buffer, - GtkTextIter* location, - GtkTextMark* mark) { - if (mark == instant_mark_ || !instant_mark_) - return; - - GtkTextIter new_iter = *location; - ValidateTextBufferIter(&new_iter); - - static guint signal_id = g_signal_lookup("mark-set", GTK_TYPE_TEXT_BUFFER); - - // "mark-set" signal is actually emitted after the mark's location is already - // set, so if the location is beyond the instant anchor, we need to move the - // mark again, which will emit the signal again. In order to prevent other - // signal handlers from being called twice, we need to stop signal emission - // before moving the mark again. - if (gtk_text_iter_compare(&new_iter, location)) { - g_signal_stop_emission(buffer, signal_id, 0); - gtk_text_buffer_move_mark(buffer, mark, &new_iter); - return; - } - - if (mark != gtk_text_buffer_get_insert(text_buffer_) && - mark != gtk_text_buffer_get_selection_bound(text_buffer_)) { - return; - } - - // See issue http://crbug.com/63860 - GtkTextIter insert; - GtkTextIter selection_bound; - gtk_text_buffer_get_iter_at_mark(buffer, &insert, - gtk_text_buffer_get_insert(buffer)); - gtk_text_buffer_get_iter_at_mark(buffer, &selection_bound, - gtk_text_buffer_get_selection_bound(buffer)); - - GtkTextIter end; - gtk_text_buffer_get_iter_at_mark(text_buffer_, &end, instant_mark_); - - if (gtk_text_iter_compare(&insert, &end) > 0 || - gtk_text_iter_compare(&selection_bound, &end) > 0) { - g_signal_stop_emission(buffer, signal_id, 0); - } -} - -// static -void AutocompleteEditViewGtk::ClipboardGetSelectionThunk( - GtkClipboard* clipboard, - GtkSelectionData* selection_data, - guint info, - gpointer object) { - AutocompleteEditViewGtk* edit_view = - reinterpret_cast<AutocompleteEditViewGtk*>( - g_object_get_data(G_OBJECT(object), kAutocompleteEditViewGtkKey)); - edit_view->ClipboardGetSelection(clipboard, selection_data, info); -} - -void AutocompleteEditViewGtk::ClipboardGetSelection( - GtkClipboard* clipboard, - GtkSelectionData* selection_data, - guint info) { - gtk_selection_data_set_text(selection_data, primary_selection_text_.c_str(), - primary_selection_text_.size()); -} - -std::string AutocompleteEditViewGtk::GetSelectedText() const { - GtkTextIter start, end; - std::string result; - if (gtk_text_buffer_get_selection_bounds(text_buffer_, &start, &end)) { - gchar* text = gtk_text_iter_get_text(&start, &end); - size_t text_len = strlen(text); - if (text_len) - result = std::string(text, text_len); - g_free(text); - } - return result; -} - -void AutocompleteEditViewGtk::UpdatePrimarySelectionIfValidURL() { - string16 text = UTF8ToUTF16(GetSelectedText()); - - if (text.empty()) - return; - - // Use AdjustTextForCopy to make sure we prefix the text with 'http://'. - CharRange selection = GetSelection(); - GURL url; - bool write_url; - model_->AdjustTextForCopy(selection.selection_min(), IsSelectAll(), &text, - &url, &write_url); - if (write_url) { - selected_text_ = UTF16ToUTF8(text); - OwnPrimarySelection(selected_text_); - } -} - -#if GTK_CHECK_VERSION(2, 20, 0) -void AutocompleteEditViewGtk::HandlePreeditChanged(GtkWidget* sender, - const gchar* preedit) { - // GtkTextView won't fire "begin-user-action" and "end-user-action" signals - // when changing the preedit string, so we need to call - // OnBeforePossibleChange() and OnAfterPossibleChange() by ourselves. - OnBeforePossibleChange(); - if (preedit && *preedit) { - // GtkTextView will only delete the selection range when committing the - // preedit string, which will cause very strange behavior, so we need to - // delete the selection range here explicitly. See http://crbug.com/18808. - if (preedit_.empty()) - gtk_text_buffer_delete_selection(text_buffer_, false, true); - preedit_ = UTF8ToUTF16(preedit); - } else { - preedit_.clear(); - } - OnAfterPossibleChange(); -} -#endif - -void AutocompleteEditViewGtk::HandleWindowSetFocus( - GtkWindow* sender, GtkWidget* focus) { - // This is actually a guess. If the focused widget changes in "focus-out" - // event handler, then the window will respect that and won't focus - // |focus|. I doubt that is likely to happen however. - going_to_focus_ = focus; -} - -void AutocompleteEditViewGtk::HandleUndoRedo(GtkWidget* sender) { - OnBeforePossibleChange(); -} - -void AutocompleteEditViewGtk::HandleUndoRedoAfter(GtkWidget* sender) { - OnAfterPossibleChange(); -} - -void AutocompleteEditViewGtk::GetTextBufferBounds(GtkTextIter* start, - GtkTextIter* end) const { - gtk_text_buffer_get_start_iter(text_buffer_, start); - gtk_text_buffer_get_iter_at_mark(text_buffer_, end, instant_mark_); -} - -void AutocompleteEditViewGtk::ValidateTextBufferIter(GtkTextIter* iter) const { - if (!instant_mark_) - return; - - GtkTextIter end; - gtk_text_buffer_get_iter_at_mark(text_buffer_, &end, instant_mark_); - if (gtk_text_iter_compare(iter, &end) > 0) - *iter = end; -} - -void AutocompleteEditViewGtk::AdjustVerticalAlignmentOfInstantView() { - // By default, GtkTextView layouts an anchored child widget just above the - // baseline, so we need to move the |instant_view_| down to make sure it - // has the same baseline as the |text_view_|. - PangoLayout* layout = gtk_label_get_layout(GTK_LABEL(instant_view_)); - int height; - pango_layout_get_size(layout, NULL, &height); - PangoLayoutIter* iter = pango_layout_get_iter(layout); - int baseline = pango_layout_iter_get_baseline(iter); - pango_layout_iter_free(iter); - g_object_set(instant_anchor_tag_, "rise", baseline - height, NULL); -} |