Index: chrome/browser/autocomplete/autocomplete_edit_view_gtk.cc |
diff --git a/chrome/browser/autocomplete/autocomplete_edit_view_gtk.cc b/chrome/browser/autocomplete/autocomplete_edit_view_gtk.cc |
new file mode 100644 |
index 0000000000000000000000000000000000000000..cae7c6e9480dd13dbeaf4c091da45763b2a15d29 |
--- /dev/null |
+++ b/chrome/browser/autocomplete/autocomplete_edit_view_gtk.cc |
@@ -0,0 +1,483 @@ |
+// Copyright (c) 2009 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 <gtk/gtk.h> |
+ |
+#include "base/logging.h" |
+#include "base/string_util.h" |
+#include "chrome/browser/autocomplete/autocomplete_edit.h" |
+#include "chrome/browser/autocomplete/autocomplete_popup_model.h" |
+#include "chrome/browser/autocomplete/autocomplete_popup_view_gtk.h" |
+#include "chrome/browser/tab_contents/tab_contents.h" |
+#include "chrome/browser/toolbar_model.h" |
+#include "chrome/common/notification_service.h" |
+#include "googleurl/src/gurl.h" |
+ |
+namespace { |
+ |
+const char kTextBaseColor[] = "#808080"; |
+const char kSecureSchemeColor[] = "#009614"; |
+const char kInsecureSchemeColor[] = "#009614"; |
+const GdkColor kSecureBackgroundColor = {0, 65535, 62965, 50115}; // #fff5c3 |
+const GdkColor kInsecureBackgroundColor = {0, 65535, 65535, 65535}; // #ffffff |
+ |
+} // namespace |
+ |
+AutocompleteEditViewGtk::AutocompleteEditViewGtk( |
+ AutocompleteEditController* controller, |
+ ToolbarModel* toolbar_model, |
+ Profile* profile, |
+ CommandUpdater* command_updater) |
+ : text_view_(NULL), |
+ tag_table_(NULL), |
+ text_buffer_(NULL), |
+ base_tag_(NULL), |
+ secure_scheme_tag_(NULL), |
+ insecure_scheme_tag_(NULL), |
+ model_(new AutocompleteEditModel(this, controller, profile)), |
+ popup_view_(new AutocompletePopupViewGtk(this, model_.get(), profile)), |
+ controller_(controller), |
+ toolbar_model_(toolbar_model), |
+ command_updater_(command_updater), |
+ popup_window_mode_(false), // TODO(deanm) |
+ scheme_security_level_(ToolbarModel::NORMAL) { |
+ model_->set_popup_model(popup_view_->model()); |
+} |
+ |
+AutocompleteEditViewGtk::~AutocompleteEditViewGtk() { |
+ NotificationService::current()->Notify( |
+ NotificationType::AUTOCOMPLETE_EDIT_DESTROYED, |
+ Source<AutocompleteEditViewGtk>(this), |
+ NotificationService::NoDetails()); |
+} |
+ |
+void AutocompleteEditViewGtk::Init() { |
+ tag_table_ = gtk_text_tag_table_new(); |
+ text_buffer_ = gtk_text_buffer_new(tag_table_); |
+ text_view_ = gtk_text_view_new_with_buffer(text_buffer_); |
+ |
+ gtk_text_view_set_left_margin(GTK_TEXT_VIEW(text_view_), 4); |
+ gtk_text_view_set_right_margin(GTK_TEXT_VIEW(text_view_), 4); |
+ |
+ // TODO(deanm): This is a super lame attempt to vertically center our single |
+ // line of text in a multiline edit control. Mannnn. |
+ gtk_text_view_set_pixels_above_lines(GTK_TEXT_VIEW(text_view_), 4); |
+ |
+ // TODO(deanm): This will probably have to be handled differently with the |
+ // tab to search business. Maybe we should just eat the tab characters. |
+ // We want the tab key to move focus, not insert a tab. |
+ gtk_text_view_set_accepts_tab(GTK_TEXT_VIEW(text_view_), false); |
+ |
+ base_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); |
+ insecure_scheme_tag_ = gtk_text_buffer_create_tag(text_buffer_, |
+ NULL, "foreground", kInsecureSchemeColor, 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); |
+ g_signal_connect(text_view_, "size-request", |
+ G_CALLBACK(&HandleViewSizeRequest), this); |
+ g_signal_connect(text_view_, "button-press-event", |
+ G_CALLBACK(&HandleViewButtonPressThunk), 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); |
+} |
+ |
+void AutocompleteEditViewGtk::FocusLocation() { |
+ gtk_widget_grab_focus(text_view_); |
+ SelectAll(false); |
+} |
+ |
+void AutocompleteEditViewGtk::SaveStateToTab(TabContents* tab) { |
+ NOTIMPLEMENTED(); |
+} |
+ |
+void AutocompleteEditViewGtk::Update(const TabContents* contents) { |
+ // NOTE: We're getting the URL text here from the ToolbarModel. |
+ bool visibly_changed_permanent_text = |
+ model_->UpdatePermanentText(toolbar_model_->GetText()); |
+ |
+ ToolbarModel::SecurityLevel security_level = |
+ toolbar_model_->GetSchemeSecurityLevel(); |
+ bool changed_security_level = (security_level != scheme_security_level_); |
+ scheme_security_level_ = security_level; |
+ |
+ if (contents) { |
+ RevertAll(); |
+ // TODO(deanm): Tab switching. The Windows code puts some state in a |
+ // PropertyBag on the tab contents, and restores state from there. |
+ } 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 std::wstring& keyword) { |
+ if (!url.is_valid()) |
+ return; |
+ |
+ model_->SendOpenNotification(selected_line, keyword); |
+ |
+ if (disposition != NEW_BACKGROUND_TAB) |
+ RevertAll(); // Revert the box to its unedited state |
+ controller_->OnAutocompleteAccept(url, disposition, transition, |
+ alternate_nav_url); |
+} |
+ |
+std::wstring AutocompleteEditViewGtk::GetText() const { |
+ GtkTextIter start, end; |
+ gtk_text_buffer_get_bounds(text_buffer_, &start, &end); |
+ gchar* utf8 = gtk_text_buffer_get_text(text_buffer_, &start, &end, false); |
+ std::wstring out(UTF8ToWide(utf8)); |
+ g_free(utf8); |
+ return out; |
+} |
+ |
+void AutocompleteEditViewGtk::SetUserText(const std::wstring& text, |
+ const std::wstring& display_text, |
+ bool update_popup) { |
+ NOTIMPLEMENTED(); |
+} |
+ |
+void AutocompleteEditViewGtk::SetWindowTextAndCaretPos(const std::wstring& text, |
+ size_t caret_pos) { |
+ std::string utf8 = WideToUTF8(text); |
+ gtk_text_buffer_set_text(text_buffer_, utf8.data(), utf8.length()); |
+ EmphasizeURLComponents(); |
+ |
+ GtkTextIter cur_pos; |
+ gtk_text_buffer_get_iter_at_offset(text_buffer_, &cur_pos, caret_pos); |
+ gtk_text_buffer_place_cursor(text_buffer_, &cur_pos); |
+} |
+ |
+bool AutocompleteEditViewGtk::IsSelectAll() { |
+ NOTIMPLEMENTED(); |
+ return false; |
+} |
+ |
+void AutocompleteEditViewGtk::SelectAll(bool reversed) { |
+ GtkTextIter start, end; |
+ if (reversed) { |
+ gtk_text_buffer_get_bounds(text_buffer_, &end, &start); |
+ } else { |
+ gtk_text_buffer_get_bounds(text_buffer_, &start, &end); |
+ } |
+ gtk_text_buffer_place_cursor(text_buffer_, &start); |
+ gtk_text_buffer_select_range(text_buffer_, &start, &end); |
+} |
+ |
+void AutocompleteEditViewGtk::RevertAll() { |
+ ClosePopup(); |
+ model_->Revert(); |
+ TextChanged(); |
+} |
+ |
+void AutocompleteEditViewGtk::UpdatePopup() { |
+ model_->SetInputInProgress(true); |
+ if (!model_->has_focus()) |
+ return; |
+ |
+ // Don't inline autocomplete when the caret/selection isn't at the end of |
+ // the text. |
+ CharRange sel = GetSelection(); |
+ model_->StartAutocomplete(sel.cp_max < GetTextLength()); |
+} |
+ |
+void AutocompleteEditViewGtk::ClosePopup() { |
+ popup_view_->model()->StopAutocomplete(); |
+} |
+ |
+void AutocompleteEditViewGtk::OnTemporaryTextMaybeChanged( |
+ const std::wstring& display_text, |
+ bool save_original_selection) { |
+ // TODO(deanm): Ignoring save_original_selection here, etc. |
+ SetWindowTextAndCaretPos(display_text, display_text.length()); |
+ TextChanged(); |
+} |
+ |
+bool AutocompleteEditViewGtk::OnInlineAutocompleteTextMaybeChanged( |
+ const std::wstring& display_text, |
+ size_t user_text_length) { |
+ if (display_text == GetText()) |
+ return false; |
+ |
+ SetWindowTextAndCaretPos(display_text, 0); |
+ |
+ // Select the part of the text that was inline autocompleted. |
+ GtkTextIter bound, insert; |
+ gtk_text_buffer_get_bounds(text_buffer_, &insert, &bound); |
+ gtk_text_buffer_get_iter_at_offset(text_buffer_, &insert, user_text_length); |
+ gtk_text_buffer_select_range(text_buffer_, &insert, &bound); |
+ |
+ TextChanged(); |
+ return true; |
+} |
+ |
+void AutocompleteEditViewGtk::OnRevertTemporaryText() { |
+ NOTIMPLEMENTED(); |
+} |
+ |
+void AutocompleteEditViewGtk::OnBeforePossibleChange() { |
+ // Record our state. |
+ text_before_change_ = GetText(); |
+ sel_before_change_ = GetSelection(); |
+} |
+ |
+// TODO(deanm): This is mostly stolen from Windows, and will need some work. |
+bool AutocompleteEditViewGtk::OnAfterPossibleChange() { |
+ CharRange new_sel = GetSelection(); |
+ int length = GetTextLength(); |
+ bool selection_differs = (new_sel.cp_min != sel_before_change_.cp_min) || |
+ (new_sel.cp_max != sel_before_change_.cp_max); |
+ 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(). |
+ std::wstring new_text(GetText()); |
+ bool text_differs = (new_text != text_before_change_); |
+ |
+ // 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.) |
+ 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)); |
+ |
+ bool something_changed = model_->OnAfterPossibleChange(new_text, |
+ selection_differs, text_differs, just_deleted_text, at_end_of_edit); |
+ |
+ if (something_changed && text_differs) |
+ TextChanged(); |
+ |
+ return something_changed; |
+} |
+ |
+void AutocompleteEditViewGtk::BottomLeftPosWidth(int* x, int* y, int* width) { |
+ gdk_window_get_origin(text_view_->window, x, y); |
+ *y += text_view_->allocation.height; |
+ *width = text_view_->allocation.width; |
+} |
+ |
+void AutocompleteEditViewGtk::HandleBeginUserAction() { |
+ OnBeforePossibleChange(); |
+} |
+ |
+void AutocompleteEditViewGtk::HandleEndUserAction() { |
+ bool had_newline = false; |
+ |
+ // TODO(deanm): obviously super inefficient. |
+ for(;;) { |
+ GtkTextIter cur, end; |
+ gtk_text_buffer_get_bounds(text_buffer_, &cur, &end); |
+ |
+ while (!gtk_text_iter_equal(&cur, &end)) { |
+ if (gtk_text_iter_ends_line(&cur)) { |
+ had_newline = true; |
+ GtkTextIter next = cur; |
+ gtk_text_iter_forward_char(&next); |
+ gtk_text_buffer_delete(text_buffer_, &cur, &next); |
+ |
+ // We've invalidated our iterators, gotta start again. |
+ break; |
+ } |
+ |
+ gtk_text_iter_forward_char(&cur); |
+ } |
+ |
+ // We've exhausted the whole input and there is now only 1 line, good. |
+ if (gtk_text_iter_equal(&cur, &end)) |
+ break; |
+ } |
+ |
+ OnAfterPossibleChange(); |
+ |
+ if (had_newline) |
+ model_->AcceptInput(CURRENT_TAB, false); |
+} |
+ |
+// static |
+void AutocompleteEditViewGtk::HandleViewSizeRequest(GtkWidget* view, |
+ GtkRequisition* req, |
+ gpointer unused) { |
+ // Don't force a minimum size, allow our embedder to size us better. |
+ req->height = req->width = 1; |
+} |
+ |
+gboolean AutocompleteEditViewGtk::HandleViewButtonPress(GdkEventButton* event) { |
+ // When the GtkTextView is clicked, it will call gtk_widget_grab_focus. |
+ // I believe this causes the focus-in event to be fired before the main |
+ // clicked handling code. If we were to try to set the selection from |
+ // the focus-in event, it's just going to be undone by the click handler. |
+ // This is a bit ugly. We shim in to get the click before the GtkTextView, |
+ // then if we don't have focus, we (hopefully safely) assume that the click |
+ // will cause us to become focused. We call GtkTextView's default handler |
+ // and then stop propagation. This allows us to run our code after the |
+ // default handler, even if that handler stopped propagation. |
+ if (GTK_WIDGET_HAS_FOCUS(text_view_)) |
+ return FALSE; // Continue to propagate into the GtkTextView handler. |
+ |
+ // 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_press_event(text_view_, event); |
+ |
+ // Select the full input when we get focus. |
+ SelectAll(false); |
+ // 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; |
+ gtk_text_buffer_get_bounds(text_buffer_, &start, &end); |
+ gtk_text_view_move_visually(GTK_TEXT_VIEW(text_view_), &start, -1); |
+ |
+ return TRUE; // Don't continue, we called the default handler already. |
+} |
+ |
+gboolean AutocompleteEditViewGtk::HandleViewFocusIn() { |
+ model_->OnSetFocus(false); |
+ // TODO(deanm): Some keyword hit business, etc here. |
+ |
+ return FALSE; // Continue propagation. |
+} |
+ |
+gboolean AutocompleteEditViewGtk::HandleViewFocusOut() { |
+ // Close the popup. |
+ ClosePopup(); |
+ |
+ // Tell the model to reset itself. |
+ model_->OnKillFocus(); |
+ |
+ // TODO(deanm): This probably isn't right, and doesn't match Windows. We |
+ // don't really want to match Windows though, because it probably feels |
+ // wrong on Linux. Firefox doesn't have great behavior here also, imo. |
+ // Deselect any selection and make sure the input is at the beginning. |
+ GtkTextIter start, end; |
+ gtk_text_buffer_get_bounds(text_buffer_, &start, &end); |
+ gtk_text_buffer_place_cursor(text_buffer_, &start); |
+ return FALSE; // Pass the event on to the GtkTextView. |
+} |
+ |
+void AutocompleteEditViewGtk::HandleViewMoveCursor( |
+ GtkMovementStep step, |
+ gint count, |
+ gboolean extendion_selection) { |
+ // Handle up / down cursor movement on our own. |
+ if (step == GTK_MOVEMENT_DISPLAY_LINES) { |
+ model_->OnUpOrDownKeyPressed(count); |
+ // move-cursor doesn't use a signal accumulator on the return value (it |
+ // just ignores them), so we have to stop the propagation. |
+ g_signal_stop_emission_by_name(text_view_, "move-cursor"); |
+ return; |
+ } |
+ // Propagate into GtkTextView. |
+} |
+ |
+AutocompleteEditViewGtk::CharRange AutocompleteEditViewGtk::GetSelection() { |
+ // 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); |
+ |
+ return CharRange(gtk_text_iter_get_offset(&start), |
+ gtk_text_iter_get_offset(&insert)); |
+} |
+ |
+void AutocompleteEditViewGtk::ItersFromCharRange(const CharRange& range, |
+ GtkTextIter* iter_min, |
+ GtkTextIter* iter_max) { |
+ 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() { |
+ GtkTextIter start, end; |
+ gtk_text_buffer_get_bounds(text_buffer_, &start, &end); |
+ return gtk_text_iter_get_offset(&end); |
+} |
+ |
+void AutocompleteEditViewGtk::EmphasizeURLComponents() { |
+ // 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::Parsed parts; |
+ AutocompleteInput::Parse(GetText(), model_->GetDesiredTLD(), &parts, NULL); |
+ bool emphasize = model_->CurrentTextIsURL() && (parts.host.len > 0); |
+ |
+ // Set the baseline emphasis. |
+ GtkTextIter start, end; |
+ gtk_text_buffer_get_bounds(text_buffer_, &start, &end); |
+ gtk_text_buffer_remove_all_tags(text_buffer_, &start, &end); |
+ if (emphasize) { |
+ gtk_text_buffer_apply_tag(text_buffer_, base_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, |
+ parts.host.begin); |
+ gtk_text_buffer_get_iter_at_line_index(text_buffer_, &end, 0, |
+ parts.host.end()); |
+ gtk_text_buffer_remove_all_tags(text_buffer_, &start, &end); |
+ } |
+ |
+ // Emphasize the scheme for security UI display purposes (if necessary). |
+ if (!model_->user_input_in_progress() && parts.scheme.is_nonempty() && |
+ (scheme_security_level_ != ToolbarModel::NORMAL)) { |
+ gtk_text_buffer_get_iter_at_line_index(text_buffer_, &start, 0, |
+ parts.scheme.begin); |
+ gtk_text_buffer_get_iter_at_line_index(text_buffer_, &end, 0, |
+ parts.scheme.end()); |
+ const GdkColor* background; |
+ if (scheme_security_level_ == ToolbarModel::SECURE) { |
+ background = &kSecureBackgroundColor; |
+ gtk_text_buffer_apply_tag(text_buffer_, secure_scheme_tag_, |
+ &start, &end); |
+ } else { |
+ background = &kInsecureBackgroundColor; |
+ gtk_text_buffer_apply_tag(text_buffer_, insecure_scheme_tag_, |
+ &start, &end); |
+ } |
+ gtk_widget_modify_base(text_view_, GTK_STATE_NORMAL, background); |
+ } |
+} |
+ |
+void AutocompleteEditViewGtk::TextChanged() { |
+ EmphasizeURLComponents(); |
+ controller_->OnChanged(); |
+} |