summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorsuzhe@chromium.org <suzhe@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2010-11-02 21:57:59 +0000
committersuzhe@chromium.org <suzhe@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2010-11-02 21:57:59 +0000
commite1d081d0a287a7c5541af2c46444f73f2957cd41 (patch)
tree56cc8808805934428267acad6e07b9b1c730be01
parentea161da013c64b7d63921a3a23289a667d284e0e (diff)
downloadchromium_src-e1d081d0a287a7c5541af2c46444f73f2957cd41.zip
chromium_src-e1d081d0a287a7c5541af2c46444f73f2957cd41.tar.gz
chromium_src-e1d081d0a287a7c5541af2c46444f73f2957cd41.tar.bz2
[Linux] Improve preedit string and Instant suggestion support in omnibox.
This CL contains following major changes: 1. Uses GtkLabel instead of GtkTextView for displaying instant suggestion. 2. Attaches the instant view to a child anchor in the text view. 3. Treats preedit string as part of user input, so that autocomplete match can take effect with uncommitted preedit string. BUG=27547 TEST=Instant suggestion should work as normal. And preedit string should trigger autocomplete match and work with Instant suggestion. Review URL: http://codereview.chromium.org/4202005 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@64825 0039d316-1c4b-4281-b951-d872f2087c98
-rw-r--r--chrome/browser/autocomplete/autocomplete_edit_view_browsertest.cc125
-rw-r--r--chrome/browser/autocomplete/autocomplete_edit_view_gtk.cc298
-rw-r--r--chrome/browser/autocomplete/autocomplete_edit_view_gtk.h46
3 files changed, 383 insertions, 86 deletions
diff --git a/chrome/browser/autocomplete/autocomplete_edit_view_browsertest.cc b/chrome/browser/autocomplete/autocomplete_edit_view_browsertest.cc
index 7a808ad..266e3d4 100644
--- a/chrome/browser/autocomplete/autocomplete_edit_view_browsertest.cc
+++ b/chrome/browser/autocomplete/autocomplete_edit_view_browsertest.cc
@@ -659,3 +659,128 @@ IN_PROC_BROWSER_TEST_F(AutocompleteEditViewTest, MAYBE_EscapeToDefaultMatch) {
EXPECT_EQ(old_text, edit_view->GetText());
EXPECT_EQ(old_selected_line, popup_model->selected_line());
}
+
+IN_PROC_BROWSER_TEST_F(AutocompleteEditViewTest, BasicTextOperations) {
+ ASSERT_NO_FATAL_FAILURE(SetupComponents());
+ ui_test_utils::NavigateToURL(browser(), GURL(chrome::kAboutBlankURL));
+ browser()->FocusLocationBar();
+
+ AutocompleteEditView* edit_view = NULL;
+ ASSERT_NO_FATAL_FAILURE(GetAutocompleteEditView(&edit_view));
+
+ std::wstring old_text = edit_view->GetText();
+ EXPECT_EQ(UTF8ToWide(chrome::kAboutBlankURL), old_text);
+ EXPECT_TRUE(edit_view->IsSelectAll());
+
+ std::wstring::size_type start, end;
+ edit_view->GetSelectionBounds(&start, &end);
+ EXPECT_EQ(0U, start);
+ EXPECT_EQ(old_text.size(), end);
+
+ // Move the cursor to the end.
+ ASSERT_NO_FATAL_FAILURE(SendKey(app::VKEY_END, false, false, false));
+ EXPECT_FALSE(edit_view->IsSelectAll());
+
+ // Make sure the cursor is placed correctly.
+ edit_view->GetSelectionBounds(&start, &end);
+ EXPECT_EQ(old_text.size(), start);
+ EXPECT_EQ(old_text.size(), end);
+
+ // Insert one character at the end. Make sure we won't insert anything after
+ // the special ZWS mark used in gtk implementation.
+ ASSERT_NO_FATAL_FAILURE(SendKey(app::VKEY_A, false, false, false));
+ EXPECT_EQ(old_text + L"a", edit_view->GetText());
+
+ // Delete one character from the end. Make sure we won't delete the special
+ // ZWS mark used in gtk implementation.
+ ASSERT_NO_FATAL_FAILURE(SendKey(app::VKEY_BACK, false, false, false));
+ EXPECT_EQ(old_text, edit_view->GetText());
+
+ edit_view->SelectAll(true);
+ EXPECT_TRUE(edit_view->IsSelectAll());
+ edit_view->GetSelectionBounds(&start, &end);
+ EXPECT_EQ(0U, start);
+ EXPECT_EQ(old_text.size(), end);
+
+ // Delete the content
+ ASSERT_NO_FATAL_FAILURE(SendKey(app::VKEY_DELETE, false, false, false));
+ EXPECT_TRUE(edit_view->IsSelectAll());
+ edit_view->GetSelectionBounds(&start, &end);
+ EXPECT_EQ(0U, start);
+ EXPECT_EQ(0U, end);
+ EXPECT_TRUE(edit_view->GetText().empty());
+
+ // Check if RevertAll() can set text and cursor correctly.
+ edit_view->RevertAll();
+ EXPECT_FALSE(edit_view->IsSelectAll());
+ EXPECT_EQ(old_text, edit_view->GetText());
+ edit_view->GetSelectionBounds(&start, &end);
+ EXPECT_EQ(old_text.size(), start);
+ EXPECT_EQ(old_text.size(), end);
+}
+
+#if defined(OS_LINUX)
+IN_PROC_BROWSER_TEST_F(AutocompleteEditViewTest, UndoRedoLinux) {
+ ASSERT_NO_FATAL_FAILURE(SetupComponents());
+ ui_test_utils::NavigateToURL(browser(), GURL(chrome::kAboutBlankURL));
+ browser()->FocusLocationBar();
+
+ AutocompleteEditView* edit_view = NULL;
+ ASSERT_NO_FATAL_FAILURE(GetAutocompleteEditView(&edit_view));
+
+ std::wstring old_text = edit_view->GetText();
+ EXPECT_EQ(UTF8ToWide(chrome::kAboutBlankURL), old_text);
+ EXPECT_TRUE(edit_view->IsSelectAll());
+
+ // Undo should clear the omnibox.
+ ASSERT_NO_FATAL_FAILURE(SendKey(app::VKEY_Z, true, false, false));
+ EXPECT_TRUE(edit_view->GetText().empty());
+
+ // Nothing should happen if undo again.
+ ASSERT_NO_FATAL_FAILURE(SendKey(app::VKEY_Z, true, false, false));
+ EXPECT_TRUE(edit_view->GetText().empty());
+
+ // Redo should restore the original text.
+ ASSERT_NO_FATAL_FAILURE(SendKey(app::VKEY_Z, true, true, false));
+ EXPECT_EQ(old_text, edit_view->GetText());
+
+ // Looks like the undo manager doesn't support restoring selection.
+ EXPECT_FALSE(edit_view->IsSelectAll());
+
+ // The cursor should be at the end.
+ std::wstring::size_type start, end;
+ edit_view->GetSelectionBounds(&start, &end);
+ EXPECT_EQ(old_text.size(), start);
+ EXPECT_EQ(old_text.size(), end);
+
+ // Delete two characters.
+ ASSERT_NO_FATAL_FAILURE(SendKey(app::VKEY_BACK, false, false, false));
+ ASSERT_NO_FATAL_FAILURE(SendKey(app::VKEY_BACK, false, false, false));
+ EXPECT_EQ(old_text.substr(0, old_text.size() - 2), edit_view->GetText());
+
+ // Undo delete.
+ ASSERT_NO_FATAL_FAILURE(SendKey(app::VKEY_Z, true, false, false));
+ EXPECT_EQ(old_text, edit_view->GetText());
+
+ // Redo delete.
+ ASSERT_NO_FATAL_FAILURE(SendKey(app::VKEY_Z, true, true, false));
+ EXPECT_EQ(old_text.substr(0, old_text.size() - 2), edit_view->GetText());
+
+ // Delete everything.
+ edit_view->SelectAll(true);
+ ASSERT_NO_FATAL_FAILURE(SendKey(app::VKEY_BACK, false, false, false));
+ EXPECT_TRUE(edit_view->GetText().empty());
+
+ // Undo delete everything.
+ ASSERT_NO_FATAL_FAILURE(SendKey(app::VKEY_Z, true, false, false));
+ EXPECT_EQ(old_text.substr(0, old_text.size() - 2), edit_view->GetText());
+
+ // Undo delete two characters.
+ ASSERT_NO_FATAL_FAILURE(SendKey(app::VKEY_Z, true, false, false));
+ EXPECT_EQ(old_text, edit_view->GetText());
+
+ // Undo again.
+ ASSERT_NO_FATAL_FAILURE(SendKey(app::VKEY_Z, true, false, false));
+ EXPECT_TRUE(edit_view->GetText().empty());
+}
+#endif
diff --git a/chrome/browser/autocomplete/autocomplete_edit_view_gtk.cc b/chrome/browser/autocomplete/autocomplete_edit_view_gtk.cc
index cf7103e..0048ae1 100644
--- a/chrome/browser/autocomplete/autocomplete_edit_view_gtk.cc
+++ b/chrome/browser/autocomplete/autocomplete_edit_view_gtk.cc
@@ -150,6 +150,10 @@ AutocompleteEditViewGtk::AutocompleteEditViewGtk(
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)),
#if defined(TOOLKIT_VIEWS)
popup_view_(new AutocompletePopupContentsView(
@@ -218,6 +222,15 @@ void AutocompleteEditViewGtk::Init() {
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);
@@ -255,8 +268,6 @@ void AutocompleteEditViewGtk::Init() {
G_CALLBACK(&HandleBeginUserActionThunk), this);
g_signal_connect(text_buffer_, "end-user-action",
G_CALLBACK(&HandleEndUserActionThunk), this);
- g_signal_connect(text_buffer_, "insert-text",
- G_CALLBACK(&HandleInsertTextThunk), 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);
@@ -305,23 +316,55 @@ void AutocompleteEditViewGtk::Init() {
G_CALLBACK(&HandleDeleteFromCursorThunk), this);
g_signal_connect(text_view_, "hierarchy-changed",
G_CALLBACK(&HandleHierarchyChangedThunk), this);
-#if GTK_CHECK_VERSION(2,20,0)
+#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);
// Setup for the Instant suggestion text view.
- instant_view_ = gtk_text_view_new();
- instant_buffer_ = gtk_text_view_get_buffer(GTK_TEXT_VIEW(instant_view_));
- gtk_text_view_add_child_in_window(GTK_TEXT_VIEW(text_view_),
+ // GtkLabel is used instead of GtkTextView to get transparent background.
+ instant_view_ = gtk_label_new(NULL);
+
+ 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_,
- GTK_TEXT_WINDOW_WIDGET,
- 0, 0);
- instant_text_tag_ = gtk_text_buffer_create_tag(
- instant_buffer_, NULL, "foreground", kTextBaseColor, NULL);
- GTK_WIDGET_UNSET_FLAGS(instant_view_, GTK_CAN_FOCUS);
- g_signal_connect(instant_view_, "button-press-event",
- G_CALLBACK(&HandleInstantViewButtonPressThunk), this);
+ 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();
#if !defined(TOOLKIT_VIEWS)
registrar_.Add(this,
@@ -364,6 +407,9 @@ int AutocompleteEditViewGtk::TextWidth() {
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);
@@ -445,12 +491,27 @@ void AutocompleteEditViewGtk::OpenURL(const GURL& url,
}
std::wstring AutocompleteEditViewGtk::GetText() const {
- return GetTextFromBuffer(text_buffer_);
+ GtkTextIter start, end;
+ GetTextBufferBounds(&start, &end);
+ gchar* utf8 = gtk_text_buffer_get_text(text_buffer_, &start, &end, false);
+ std::wstring out(UTF8ToWide(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() ||
- (gtk_text_buffer_get_char_count(text_buffer_) == 0);
+ return model_->user_input_in_progress() || (GetTextLength() == 0);
}
int AutocompleteEditViewGtk::GetIcon() const {
@@ -497,7 +558,7 @@ bool AutocompleteEditViewGtk::IsSelectAll() {
gtk_text_buffer_get_selection_bounds(text_buffer_, &sel_start, &sel_end);
GtkTextIter start, end;
- gtk_text_buffer_get_bounds(text_buffer_, &start, &end);
+ GetTextBufferBounds(&start, &end);
// Returns true if the |text_buffer_| is empty.
return gtk_text_iter_equal(&start, &sel_start) &&
@@ -530,10 +591,14 @@ void AutocompleteEditViewGtk::UpdatePopup() {
return;
// Don't inline autocomplete when the caret/selection isn't at the end of
- // the text.
+ // the text, or in the middle of composition.
CharRange sel = GetSelection();
- model_->StartAutocomplete(sel.cp_min != sel.cp_max,
- std::max(sel.cp_max, sel.cp_min) < GetTextLength());
+ bool no_inline_autocomplete =
+ std::max(sel.cp_max, sel.cp_min) < GetTextLength();
+#if GTK_CHECK_VERSION(2, 20, 0)
+ no_inline_autocomplete = no_inline_autocomplete || preedit_.size();
+#endif
+ model_->StartAutocomplete(sel.cp_min != sel.cp_max, no_inline_autocomplete);
}
void AutocompleteEditViewGtk::ClosePopup() {
@@ -674,6 +739,7 @@ void AutocompleteEditViewGtk::SetBaseColor() {
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_);
@@ -684,9 +750,11 @@ void AutocompleteEditViewGtk::SetBaseColor() {
style->text[GTK_STATE_NORMAL], style->base[GTK_STATE_NORMAL]);
g_object_set(faded_text_tag_, "foreground-gdk", &average_color, NULL);
- g_object_set(instant_text_tag_, "foreground-gdk", &average_color, NULL);
g_object_set(normal_text_tag_, "foreground-gdk",
&style->text[GTK_STATE_NORMAL], NULL);
+
+ // GtkLabel uses fg color instead of text color.
+ gtk_widget_modify_fg(instant_view_, GTK_STATE_NORMAL, &average_color);
} else {
const GdkColor* background_color_ptr;
#if defined(TOOLKIT_VIEWS)
@@ -701,10 +769,10 @@ void AutocompleteEditViewGtk::SetBaseColor() {
text_view_, &gtk_util::kGdkBlack, &gtk_util::kGdkGray);
gtk_widget_modify_base(text_view_, GTK_STATE_NORMAL, background_color_ptr);
+ GdkColor c;
#if !defined(TOOLKIT_VIEWS)
// Override the selected colors so we don't leak colors from the current
// gtk theme into the chrome-theme.
- GdkColor c;
c = gfx::SkColorToGdkColor(
theme_provider_->get_active_selection_bg_color());
gtk_widget_modify_base(text_view_, GTK_STATE_SELECTED, &c);
@@ -722,16 +790,25 @@ void AutocompleteEditViewGtk::SetBaseColor() {
gtk_widget_modify_text(text_view_, GTK_STATE_ACTIVE, &c);
#endif
+ gdk_color_parse(kTextBaseColor, &c);
+ gtk_widget_modify_fg(instant_view_, GTK_STATE_NORMAL, &c);
+
// Until we switch to vector graphics, force the font size.
gtk_util::ForceFontSizePixels(text_view_,
popup_window_mode_ ?
browser_defaults::kAutocompleteEditFontPixelSizeInPopup :
browser_defaults::kAutocompleteEditFontPixelSize);
+ gtk_util::ForceFontSizePixels(instant_view_,
+ popup_window_mode_ ?
+ browser_defaults::kAutocompleteEditFontPixelSizeInPopup :
+ browser_defaults::kAutocompleteEditFontPixelSize);
+
g_object_set(faded_text_tag_, "foreground", kTextBaseColor, NULL);
- g_object_set(instant_text_tag_, "foreground", kTextBaseColor, NULL);
g_object_set(normal_text_tag_, "foreground", "#000000", NULL);
}
+
+ AdjustVerticalAlignmentOfInstantView();
}
void AutocompleteEditViewGtk::HandleBeginUserAction(GtkTextBuffer* sender) {
@@ -946,7 +1023,7 @@ gboolean AutocompleteEditViewGtk::HandleViewButtonRelease(
// 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;
- gtk_text_buffer_get_bounds(text_buffer_, &start, &end);
+ GetTextBufferBounds(&start, &end);
gtk_text_view_move_visually(GTK_TEXT_VIEW(text_view_), &start, -1);
}
#endif
@@ -1025,7 +1102,7 @@ void AutocompleteEditViewGtk::HandleViewMoveCursor(
gint cursor_pos;
g_object_get(G_OBJECT(text_buffer_), "cursor-position", &cursor_pos,
NULL);
- if (cursor_pos == gtk_text_buffer_get_char_count(text_buffer_))
+ if (cursor_pos == GetTextLength())
controller_->OnCommitSuggestedText(GetText());
else
handled = false;
@@ -1203,8 +1280,8 @@ void AutocompleteEditViewGtk::HandleInsertText(
filtered_text.reserve(len);
// Filter out new line and tab characters.
- // |text| is guaranteed to be a valid UTF-8 string, so it's safe here to
- // filter byte by byte.
+ // |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
@@ -1213,15 +1290,25 @@ void AutocompleteEditViewGtk::HandleInsertText(
if (len == 1 && (text[0] == '\n' || text[0] == '\r'))
enter_was_inserted_ = true;
- for (gint i = 0; i < len; ++i) {
- gchar c = text[i];
- if (c == '\n' || c == '\r' || c == '\t')
- continue;
+ const gchar* p = text;
+ while(*p) {
+ 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);
- filtered_text.push_back(c);
+ 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(),
@@ -1410,9 +1497,9 @@ void AutocompleteEditViewGtk::SelectAllInternal(bool reversed,
bool update_primary_selection) {
GtkTextIter start, end;
if (reversed) {
- gtk_text_buffer_get_bounds(text_buffer_, &end, &start);
+ GetTextBufferBounds(&end, &start);
} else {
- gtk_text_buffer_get_bounds(text_buffer_, &start, &end);
+ GetTextBufferBounds(&start, &end);
}
if (!update_primary_selection)
StartUpdatingHighlightedText();
@@ -1459,6 +1546,11 @@ AutocompleteEditViewGtk::CharRange AutocompleteEditViewGtk::GetSelection() {
mark = gtk_text_buffer_get_insert(text_buffer_);
gtk_text_buffer_get_iter_at_mark(text_buffer_, &insert, mark);
+#if GTK_CHECK_VERSION(2, 20, 0)
+ // Nothing should be selected when we are in the middle of composition.
+ DCHECK(preedit_.empty() || gtk_text_iter_equal(&start, &insert));
+#endif
+
return CharRange(gtk_text_iter_get_offset(&start),
gtk_text_iter_get_offset(&insert));
}
@@ -1470,23 +1562,28 @@ void AutocompleteEditViewGtk::ItersFromCharRange(const CharRange& range,
gtk_text_buffer_get_iter_at_offset(text_buffer_, iter_max, range.cp_max);
}
-int AutocompleteEditViewGtk::GetTextLength() {
- GtkTextIter start, end;
- gtk_text_buffer_get_bounds(text_buffer_, &start, &end);
+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);
-}
-
-std::wstring AutocompleteEditViewGtk::GetTextFromBuffer(
- GtkTextBuffer* buffer) const {
- GtkTextIter start, end;
- gtk_text_buffer_get_bounds(buffer, &start, &end);
- gchar* utf8 = gtk_text_buffer_get_text(buffer, &start, &end, false);
- std::wstring out(UTF8ToWide(utf8));
- g_free(utf8);
- return out;
+#endif
}
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())
+ 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
@@ -1501,7 +1598,7 @@ void AutocompleteEditViewGtk::EmphasizeURLComponents() {
// Set the baseline emphasis.
GtkTextIter start, end;
- gtk_text_buffer_get_bounds(text_buffer_, &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);
@@ -1545,28 +1642,22 @@ void AutocompleteEditViewGtk::EmphasizeURLComponents() {
void AutocompleteEditViewGtk::SetInstantSuggestion(
const std::string& suggestion) {
+ gtk_label_set_text(GTK_LABEL(instant_view_), suggestion.c_str());
if (suggestion.empty()) {
gtk_widget_hide(instant_view_);
} else {
- gtk_text_buffer_set_text(instant_buffer_, suggestion.data(),
- suggestion.size());
- GtkTextIter start, end;
- gtk_text_buffer_get_bounds(instant_buffer_, &start, &end);
- gtk_text_buffer_apply_tag(instant_buffer_, instant_text_tag_, &start, &end);
gtk_widget_show(instant_view_);
+ AdjustVerticalAlignmentOfInstantView();
}
}
bool AutocompleteEditViewGtk::CommitInstantSuggestion() {
- if (!GTK_WIDGET_VISIBLE(instant_view_))
- return false;
-
- std::wstring suggest_text = GetTextFromBuffer(instant_buffer_);
- if (suggest_text.empty())
+ const gchar* suggestion = gtk_label_get_text(GTK_LABEL(instant_view_));
+ if (!suggestion || !*suggestion)
return false;
OnBeforePossibleChange();
- SetUserText(GetText() + suggest_text);
+ SetUserText(GetText() + UTF8ToWide(suggestion));
OnAfterPossibleChange();
return true;
}
@@ -1574,16 +1665,6 @@ bool AutocompleteEditViewGtk::CommitInstantSuggestion() {
void AutocompleteEditViewGtk::TextChanged() {
EmphasizeURLComponents();
controller_->OnChanged();
-
- // Move the instant suggestion to the end of the text.
- GtkTextIter start, end;
- gtk_text_buffer_get_bounds(text_buffer_, &start, &end);
- GdkRectangle rect;
- gtk_text_view_get_iter_location(GTK_TEXT_VIEW(text_view_), &end, &rect);
-
- // +1 so the cursor will show.
- gtk_text_view_move_child(GTK_TEXT_VIEW(text_view_), instant_view_,
- rect.x + rect.width + 1, 0);
}
void AutocompleteEditViewGtk::SavePrimarySelection(
@@ -1672,12 +1753,40 @@ void AutocompleteEditViewGtk::HandleKeymapDirectionChanged(GdkKeymap* sender) {
AdjustTextJustification();
}
-gboolean AutocompleteEditViewGtk::HandleInstantViewButtonPress(
- GtkWidget* sender,
- GdkEventButton* event) {
- // Clicking on the view should have no effect; just stop propagation of the
- // event.
- return TRUE;
+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_)
+ return;
+
+ GtkTextIter new_iter = *location;
+ ValidateTextBufferIter(&new_iter);
+
+ // "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)) {
+ static guint signal_id = g_signal_lookup("mark-set", GTK_TYPE_TEXT_BUFFER);
+ g_signal_stop_emission(buffer, signal_id, 0);
+ gtk_text_buffer_move_mark(buffer, mark, &new_iter);
+ }
}
// static
@@ -1731,7 +1840,7 @@ void AutocompleteEditViewGtk::UpdatePrimarySelectionIfValidURL() {
}
}
-#if GTK_CHECK_VERSION(2,20,0)
+#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
@@ -1759,3 +1868,40 @@ void AutocompleteEditViewGtk::HandleWindowSetFocus(
// |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);
+}
diff --git a/chrome/browser/autocomplete/autocomplete_edit_view_gtk.h b/chrome/browser/autocomplete/autocomplete_edit_view_gtk.h
index 34dd01c..07e0ed3 100644
--- a/chrome/browser/autocomplete/autocomplete_edit_view_gtk.h
+++ b/chrome/browser/autocomplete/autocomplete_edit_view_gtk.h
@@ -174,6 +174,12 @@ class AutocompleteEditViewGtk : public AutocompleteEditView,
GtkTextBuffer*, GtkTextIter*, const gchar*, gint);
CHROMEG_CALLBACK_0(AutocompleteEditViewGtk, void,
HandleKeymapDirectionChanged, GdkKeymap*);
+ CHROMEG_CALLBACK_2(AutocompleteEditViewGtk, void, HandleDeleteRange,
+ GtkTextBuffer*, GtkTextIter*, GtkTextIter*);
+ // Unlike above HandleMarkSet and HandleMarkSetAfter, this handler will always
+ // be connected to the signal.
+ CHROMEG_CALLBACK_2(AutocompleteEditViewGtk, void, HandleMarkSetAlways,
+ GtkTextBuffer*, GtkTextIter*, GtkTextMark*);
CHROMEGTK_CALLBACK_1(AutocompleteEditViewGtk, gboolean, HandleKeyPress,
GdkEventKey*);
@@ -214,12 +220,16 @@ class AutocompleteEditViewGtk : public AutocompleteEditView,
// listen to focus change events on it.
CHROMEGTK_CALLBACK_1(AutocompleteEditViewGtk, void, HandleHierarchyChanged,
GtkWidget*);
- CHROMEGTK_CALLBACK_1(AutocompleteEditViewGtk, gboolean,
- HandleInstantViewButtonPress, GdkEventButton*);
-#if GTK_CHECK_VERSION(2,20,0)
+#if GTK_CHECK_VERSION(2, 20, 0)
CHROMEGTK_CALLBACK_1(AutocompleteEditViewGtk, void, HandlePreeditChanged,
const gchar*);
#endif
+ // Undo/redo operations won't trigger "begin-user-action" and
+ // "end-user-action" signals, so we need to hook into "undo" and "redo"
+ // signals and call OnBeforePossibleChange()/OnAfterPossibleChange() by
+ // ourselves.
+ CHROMEGTK_CALLBACK_0(AutocompleteEditViewGtk, void, HandleUndoRedo);
+ CHROMEGTK_CALLBACK_0(AutocompleteEditViewGtk, void, HandleUndoRedoAfter);
CHROMEG_CALLBACK_1(AutocompleteEditViewGtk, void, HandleWindowSetFocus,
GtkWindow*, GtkWidget*);
@@ -269,10 +279,7 @@ class AutocompleteEditViewGtk : public AutocompleteEditView,
GtkTextIter* iter_max);
// Return the number of characers in the current buffer.
- int GetTextLength();
-
- // Get the string contents for the given buffer.
- std::wstring GetTextFromBuffer(GtkTextBuffer* buffer) const;
+ int GetTextLength() const;
// Try to parse the current text as a URL and colorize the components.
void EmphasizeURLComponents();
@@ -306,6 +313,18 @@ class AutocompleteEditViewGtk : public AutocompleteEditView,
// If the selected text parses as a URL OwnPrimarySelection is invoked.
void UpdatePrimarySelectionIfValidURL();
+ // Retrieves the first and last iterators in the |text_buffer_|, but excludes
+ // the anchor holding the |instant_view_| widget.
+ void GetTextBufferBounds(GtkTextIter* start, GtkTextIter* end) const;
+
+ // Validates an iterator in the |text_buffer_|, to make sure it doesn't go
+ // beyond the anchor for holding the |instant_view_| widget.
+ void ValidateTextBufferIter(GtkTextIter* iter) const;
+
+ // Adjusts vertical alignment of the |instant_view_| in the |text_view_|, to
+ // make sure they have the same baseline.
+ void AdjustVerticalAlignmentOfInstantView();
+
// The widget we expose, used for vertically centering the real text edit,
// since the height will change based on the font / font size, etc.
OwnedWidgetGtk alignment_;
@@ -321,9 +340,16 @@ class AutocompleteEditViewGtk : public AutocompleteEditView,
GtkTextTag* normal_text_tag_;
// Objects for the instant suggestion text view.
+ GtkTextTag* instant_anchor_tag_;
+
+ // A widget for displaying instant suggestion text. It'll be attached to a
+ // child anchor in the |text_buffer_| object.
GtkWidget* instant_view_;
- GtkTextBuffer* instant_buffer_;
- GtkTextTag* instant_text_tag_;
+
+ // A mark to split the content and the instant anchor. Wherever the end
+ // iterator of the text buffer is required, the iterator to this mark should
+ // be used.
+ GtkTextMark* instant_mark_;
scoped_ptr<AutocompleteEditModel> model_;
scoped_ptr<AutocompletePopupView> popup_view_;
@@ -434,7 +460,7 @@ class AutocompleteEditViewGtk : public AutocompleteEditView,
// is not suggested text, that means the user manually made the selection.
bool selection_suggested_;
-#if GTK_CHECK_VERSION(2,20,0)
+#if GTK_CHECK_VERSION(2, 20, 0)
// Stores the text being composed by the input method.
std::wstring preedit_;
#endif