| // 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 <gdk/gdkkeysyms.h> |
| #include <gtk/gtk.h> |
| |
| #include "views/controls/textfield/native_textfield_gtk.h" |
| |
| #include "base/logging.h" |
| #include "base/utf_string_conversions.h" |
| #include "ui/base/range/range.h" |
| #include "ui/gfx/gtk_util.h" |
| #include "ui/gfx/insets.h" |
| #include "ui/gfx/selection_model.h" |
| #include "ui/gfx/skia_utils_gtk.h" |
| #include "views/controls/textfield/gtk_views_entry.h" |
| #include "views/controls/textfield/gtk_views_textview.h" |
| #include "views/controls/textfield/native_textfield_views.h" |
| #include "views/controls/textfield/textfield.h" |
| #include "views/controls/textfield/textfield_controller.h" |
| #include "views/widget/native_widget_gtk.h" |
| |
| namespace views { |
| |
| // A character used to hide a text in password mode. |
| static const char kPasswordChar = '*'; |
| |
| // Border width for GtkTextView. |
| const int kTextViewBorderWidth = 4; |
| |
| //////////////////////////////////////////////////////////////////////////////// |
| // NativeTextfieldGtk, public: |
| |
| NativeTextfieldGtk::NativeTextfieldGtk(Textfield* textfield) |
| : textfield_(textfield), |
| paste_clipboard_requested_(false) { |
| // Make |textfield| the focused view, so that when we get focused the focus |
| // manager sees |textfield| as the focused view (since we are just a wrapper |
| // view). |
| set_focus_view(textfield); |
| } |
| |
| NativeTextfieldGtk::~NativeTextfieldGtk() { |
| } |
| |
| // Returns the inner border of an entry. |
| // static |
| gfx::Insets NativeTextfieldGtk::GetEntryInnerBorder(GtkEntry* entry) { |
| const GtkBorder* inner_border = gtk_entry_get_inner_border(entry); |
| if (inner_border) |
| return gfx::Insets(*inner_border); |
| |
| // No explicit border set, try the style. |
| GtkBorder* style_border; |
| gtk_widget_style_get(GTK_WIDGET(entry), "inner-border", &style_border, NULL); |
| if (style_border) { |
| gfx::Insets insets = gfx::Insets(*style_border); |
| gtk_border_free(style_border); |
| return insets; |
| } |
| |
| // If border is null, Gtk uses 2 on all sides. |
| return gfx::Insets(2, 2, 2, 2); |
| } |
| |
| gfx::Insets NativeTextfieldGtk::GetTextViewInnerBorder(GtkTextView* text_view) { |
| return gfx::Insets(kTextViewBorderWidth / 2, kTextViewBorderWidth / 2, |
| kTextViewBorderWidth / 2, kTextViewBorderWidth / 2); |
| } |
| |
| //////////////////////////////////////////////////////////////////////////////// |
| // NativeTextfieldGtk, NativeTextfieldWrapper implementation: |
| |
| string16 NativeTextfieldGtk::GetText() const { |
| return UTF8ToUTF16(gtk_entry_get_text(GTK_ENTRY(native_view()))); |
| } |
| |
| void NativeTextfieldGtk::UpdateText() { |
| if (!native_view()) |
| return; |
| gtk_entry_set_text(GTK_ENTRY(native_view()), |
| UTF16ToUTF8(textfield_->text()).c_str()); |
| } |
| |
| void NativeTextfieldGtk::AppendText(const string16& text) { |
| if (!native_view()) |
| return; |
| gtk_entry_append_text(GTK_ENTRY(native_view()), UTF16ToUTF8(text).c_str()); |
| } |
| |
| string16 NativeTextfieldGtk::GetSelectedText() const { |
| if (!native_view()) |
| return string16(); |
| |
| string16 result; |
| |
| gint start_pos; |
| gint end_pos; |
| if (!gtk_editable_get_selection_bounds(GTK_EDITABLE(native_view()), |
| &start_pos, &end_pos)) |
| return result; // No selection. |
| |
| UTF8ToUTF16(gtk_editable_get_chars(GTK_EDITABLE(native_view()), |
| start_pos, end_pos), |
| end_pos - start_pos, &result); |
| |
| return result; |
| } |
| |
| void NativeTextfieldGtk::SelectAll() { |
| if (!native_view()) |
| return; |
| // -1 as the end position selects to the end of the text. |
| gtk_editable_select_region(GTK_EDITABLE(native_view()), 0, -1); |
| } |
| |
| void NativeTextfieldGtk::ClearSelection() { |
| if (!native_view()) |
| return; |
| gtk_editable_select_region(GTK_EDITABLE(native_view()), 0, 0); |
| } |
| |
| void NativeTextfieldGtk::UpdateBorder() { |
| if (!native_view()) |
| return; |
| |
| if (!textfield_->draw_border()) |
| gtk_entry_set_has_frame(GTK_ENTRY(native_view()), false); |
| } |
| |
| void NativeTextfieldGtk::UpdateTextColor() { |
| if (textfield_->use_default_text_color()) { |
| // Passing NULL as the color undoes the effect of previous calls to |
| // gtk_widget_modify_text. |
| gtk_widget_modify_text(native_view(), GTK_STATE_NORMAL, NULL); |
| return; |
| } |
| GdkColor gdk_color = gfx::SkColorToGdkColor(textfield_->text_color()); |
| gtk_widget_modify_text(native_view(), GTK_STATE_NORMAL, &gdk_color); |
| } |
| |
| void NativeTextfieldGtk::UpdateBackgroundColor() { |
| if (textfield_->use_default_background_color()) { |
| // Passing NULL as the color undoes the effect of previous calls to |
| // gtk_widget_modify_base. |
| gtk_widget_modify_base(native_view(), GTK_STATE_NORMAL, NULL); |
| return; |
| } |
| GdkColor gdk_color = gfx::SkColorToGdkColor(textfield_->background_color()); |
| gtk_widget_modify_base(native_view(), GTK_STATE_NORMAL, &gdk_color); |
| } |
| |
| void NativeTextfieldGtk::UpdateReadOnly() { |
| if (!native_view()) |
| return; |
| gtk_editable_set_editable(GTK_EDITABLE(native_view()), |
| !textfield_->read_only()); |
| } |
| |
| void NativeTextfieldGtk::UpdateFont() { |
| if (!native_view()) |
| return; |
| PangoFontDescription* pfd = textfield_->font().GetNativeFont(); |
| gtk_widget_modify_font(native_view(), pfd); |
| pango_font_description_free(pfd); |
| } |
| |
| void NativeTextfieldGtk::UpdateIsPassword() { |
| if (!native_view()) |
| return; |
| gtk_entry_set_visibility(GTK_ENTRY(native_view()), !textfield_->IsPassword()); |
| } |
| |
| void NativeTextfieldGtk::UpdateEnabled() { |
| if (!native_view()) |
| return; |
| SetEnabled(textfield_->IsEnabled()); |
| } |
| |
| gfx::Insets NativeTextfieldGtk::CalculateInsets() { |
| if (!native_view()) |
| return gfx::Insets(); |
| |
| GtkWidget* widget = native_view(); |
| gfx::Insets insets; |
| |
| GtkEntry* entry = GTK_ENTRY(widget); |
| insets += GetEntryInnerBorder(entry); |
| if (entry->has_frame) { |
| insets += gfx::Insets(widget->style->ythickness, |
| widget->style->xthickness, |
| widget->style->ythickness, |
| widget->style->xthickness); |
| } |
| |
| gboolean interior_focus; |
| gint focus_width; |
| gtk_widget_style_get(widget, |
| "focus-line-width", &focus_width, |
| "interior-focus", &interior_focus, |
| NULL); |
| if (!interior_focus) |
| insets += gfx::Insets(focus_width, focus_width, focus_width, focus_width); |
| |
| return insets; |
| } |
| |
| void NativeTextfieldGtk::UpdateHorizontalMargins() { |
| if (!native_view()) |
| return; |
| |
| int left, right; |
| if (!textfield_->GetHorizontalMargins(&left, &right)) |
| return; |
| |
| gfx::Insets insets = GetEntryInnerBorder(GTK_ENTRY(native_view())); |
| GtkBorder border = {left, right, insets.top(), insets.bottom()}; |
| gtk_entry_set_inner_border(GTK_ENTRY(native_view()), &border); |
| } |
| |
| void NativeTextfieldGtk::UpdateVerticalMargins() { |
| if (!native_view()) |
| return; |
| |
| int top, bottom; |
| if (!textfield_->GetVerticalMargins(&top, &bottom)) |
| return; |
| |
| gfx::Insets insets = GetEntryInnerBorder(GTK_ENTRY(native_view())); |
| GtkBorder border = {insets.left(), insets.right(), top, bottom}; |
| gtk_entry_set_inner_border(GTK_ENTRY(native_view()), &border); |
| } |
| |
| bool NativeTextfieldGtk::SetFocus() { |
| OnFocus(); |
| return true; |
| } |
| |
| View* NativeTextfieldGtk::GetView() { |
| return this; |
| } |
| |
| gfx::NativeView NativeTextfieldGtk::GetTestingHandle() const { |
| return native_view(); |
| } |
| |
| bool NativeTextfieldGtk::IsIMEComposing() const { |
| return false; |
| } |
| |
| void NativeTextfieldGtk::GetSelectedRange(ui::Range* range) const { |
| gint start_pos; |
| gint end_pos; |
| gtk_editable_get_selection_bounds( |
| GTK_EDITABLE(native_view()), &start_pos, &end_pos); |
| *range = ui::Range(start_pos, end_pos); |
| } |
| |
| void NativeTextfieldGtk::SelectRange(const ui::Range& range) { |
| NOTREACHED(); |
| } |
| |
| void NativeTextfieldGtk::GetSelectionModel(gfx::SelectionModel* sel) const { |
| NOTREACHED(); |
| } |
| |
| void NativeTextfieldGtk::SelectSelectionModel(const gfx::SelectionModel& sel) { |
| NOTREACHED(); |
| } |
| |
| size_t NativeTextfieldGtk::GetCursorPosition() const { |
| NOTREACHED(); |
| return 0U; |
| } |
| |
| bool NativeTextfieldGtk::HandleKeyPressed(const views::KeyEvent& e) { |
| return false; |
| } |
| |
| bool NativeTextfieldGtk::HandleKeyReleased(const views::KeyEvent& e) { |
| return false; |
| } |
| |
| void NativeTextfieldGtk::HandleFocus() { |
| } |
| |
| void NativeTextfieldGtk::HandleBlur() { |
| } |
| |
| ui::TextInputClient* NativeTextfieldGtk::GetTextInputClient() { |
| return NULL; |
| } |
| |
| void NativeTextfieldGtk::ApplyStyleRange(const gfx::StyleRange& style) { |
| NOTREACHED(); |
| } |
| |
| void NativeTextfieldGtk::ApplyDefaultStyle() { |
| NOTREACHED(); |
| } |
| |
| void NativeTextfieldGtk::ClearEditHistory() { |
| NOTREACHED(); |
| } |
| |
| void NativeTextfieldGtk::OnActivate(GtkWidget* native_widget) { |
| GdkEvent* event = gtk_get_current_event(); |
| if (!event || event->type != GDK_KEY_PRESS) |
| return; |
| |
| KeyEvent views_key_event(event); |
| gboolean handled = false; |
| |
| TextfieldController* controller = textfield_->GetController(); |
| if (controller) |
| handled = controller->HandleKeyEvent(textfield_, views_key_event); |
| |
| Widget* widget = GetWidget(); |
| if (!handled && widget) { |
| NativeWidgetGtk* native_widget = |
| static_cast<NativeWidgetGtk*>(widget->native_widget()); |
| handled = native_widget->HandleKeyboardEvent(views_key_event); |
| } |
| |
| // Stop signal emission if the key event is handled by us. |
| if (handled) { |
| // Only GtkEntry has "activate" signal. |
| static guint signal_id = g_signal_lookup("activate", GTK_TYPE_ENTRY); |
| g_signal_stop_emission(native_widget, signal_id, 0); |
| } |
| } |
| |
| void NativeTextfieldGtk::OnChanged(GObject* object) { |
| // We need to call TextfieldController::ContentsChanged() explicitly if the |
| // paste action didn't change the content at all. See http://crbug.com/79002 |
| const bool call_contents_changed = |
| paste_clipboard_requested_ && GetText() == textfield_->text(); |
| textfield_->SyncText(); |
| textfield_->GetWidget()->NotifyAccessibilityEvent( |
| textfield_, ui::AccessibilityTypes::EVENT_TEXT_CHANGED, true); |
| if (call_contents_changed) { |
| TextfieldController* controller = textfield_->GetController(); |
| if (controller) |
| controller->ContentsChanged(textfield_, textfield_->text()); |
| } |
| paste_clipboard_requested_ = false; |
| } |
| |
| gboolean NativeTextfieldGtk::OnButtonPressEvent(GtkWidget* widget, |
| GdkEventButton* event) { |
| paste_clipboard_requested_ = false; |
| return false; |
| } |
| |
| gboolean NativeTextfieldGtk::OnButtonReleaseEventAfter(GtkWidget* widget, |
| GdkEventButton* event) { |
| textfield_->GetWidget()->NotifyAccessibilityEvent( |
| textfield_, ui::AccessibilityTypes::EVENT_TEXT_CHANGED, true); |
| return false; |
| } |
| |
| gboolean NativeTextfieldGtk::OnKeyPressEvent(GtkWidget* widget, |
| GdkEventKey* event) { |
| paste_clipboard_requested_ = false; |
| return false; |
| } |
| |
| gboolean NativeTextfieldGtk::OnKeyPressEventAfter(GtkWidget* widget, |
| GdkEventKey* event) { |
| TextfieldController* controller = textfield_->GetController(); |
| if (controller) { |
| KeyEvent key_event(reinterpret_cast<GdkEvent*>(event)); |
| return controller->HandleKeyEvent(textfield_, key_event); |
| } |
| return false; |
| } |
| |
| void NativeTextfieldGtk::OnMoveCursor(GtkWidget* widget, |
| GtkMovementStep step, |
| gint count, |
| gboolean extend_selection) { |
| textfield_->GetWidget()->NotifyAccessibilityEvent( |
| textfield_, ui::AccessibilityTypes::EVENT_TEXT_CHANGED, true); |
| } |
| |
| void NativeTextfieldGtk::OnPasteClipboard(GtkWidget* widget) { |
| if (!textfield_->read_only()) |
| paste_clipboard_requested_ = true; |
| } |
| |
| //////////////////////////////////////////////////////////////////////////////// |
| // NativeTextfieldGtk, NativeControlGtk overrides: |
| |
| void NativeTextfieldGtk::CreateNativeControl() { |
| NativeControlCreated(gtk_views_entry_new(this)); |
| gtk_entry_set_invisible_char(GTK_ENTRY(native_view()), |
| static_cast<gunichar>(kPasswordChar)); |
| textfield_->UpdateAllProperties(); |
| } |
| |
| void NativeTextfieldGtk::NativeControlCreated(GtkWidget* widget) { |
| NativeControlGtk::NativeControlCreated(widget); |
| |
| g_signal_connect(widget, "changed", G_CALLBACK(OnChangedThunk), this); |
| // In order to properly trigger Accelerators bound to VKEY_RETURN, we need |
| // to send an event when the widget gets the activate signal. |
| g_signal_connect(widget, "activate", G_CALLBACK(OnActivateThunk), this); |
| g_signal_connect(widget, "move-cursor", G_CALLBACK(OnMoveCursorThunk), this); |
| g_signal_connect(widget, "button-press-event", |
| G_CALLBACK(OnButtonPressEventThunk), this); |
| g_signal_connect(widget, "key-press-event", |
| G_CALLBACK(OnKeyPressEventThunk), this); |
| g_signal_connect(widget, "paste-clipboard", |
| G_CALLBACK(OnPasteClipboardThunk), this); |
| |
| g_signal_connect_after(widget, "button-release-event", |
| G_CALLBACK(OnButtonReleaseEventAfterThunk), this); |
| g_signal_connect_after(widget, "key-press-event", |
| G_CALLBACK(OnKeyPressEventAfterThunk), this); |
| } |
| |
| bool NativeTextfieldGtk::IsPassword() { |
| return textfield_->IsPassword(); |
| } |
| |
| /////////////////////////////////////////////////////////////////////////////// |
| // NativeTextfieldWrapper: |
| |
| // static |
| NativeTextfieldWrapper* NativeTextfieldWrapper::CreateWrapper( |
| Textfield* field) { |
| if (Widget::IsPureViews()) |
| return new NativeTextfieldViews(field); |
| return new NativeTextfieldGtk(field); |
| } |
| |
| } // namespace views |