OLD | NEW |
| (Empty) |
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. | |
2 // Use of this source code is governed by a BSD-style license that can be | |
3 // found in the LICENSE file. | |
4 | |
5 #include <gdk/gdkkeysyms.h> | |
6 #include <gtk/gtk.h> | |
7 | |
8 #include "ui/views/controls/textfield/native_textfield_gtk.h" | |
9 | |
10 #include "base/logging.h" | |
11 #include "base/utf_string_conversions.h" | |
12 #include "ui/base/range/range.h" | |
13 #include "ui/gfx/gtk_util.h" | |
14 #include "ui/gfx/insets.h" | |
15 #include "ui/gfx/selection_model.h" | |
16 #include "ui/gfx/skia_utils_gtk.h" | |
17 #include "ui/views/controls/textfield/gtk_views_entry.h" | |
18 #include "ui/views/controls/textfield/gtk_views_textview.h" | |
19 #include "ui/views/controls/textfield/native_textfield_views.h" | |
20 #include "ui/views/controls/textfield/textfield.h" | |
21 #include "ui/views/controls/textfield/textfield_controller.h" | |
22 #include "ui/views/widget/native_widget_gtk.h" | |
23 | |
24 namespace views { | |
25 | |
26 // A character used to hide a text in obscured mode. | |
27 static const char kObscuredChar = '*'; | |
28 | |
29 // Border width for GtkTextView. | |
30 const int kTextViewBorderWidth = 4; | |
31 | |
32 //////////////////////////////////////////////////////////////////////////////// | |
33 // NativeTextfieldGtk, public: | |
34 | |
35 NativeTextfieldGtk::NativeTextfieldGtk(Textfield* textfield) | |
36 : textfield_(textfield), | |
37 paste_clipboard_requested_(false) { | |
38 // Make |textfield| the focused view, so that when we get focused the focus | |
39 // manager sees |textfield| as the focused view (since we are just a wrapper | |
40 // view). | |
41 set_focus_view(textfield); | |
42 } | |
43 | |
44 NativeTextfieldGtk::~NativeTextfieldGtk() { | |
45 } | |
46 | |
47 // Returns the inner border of an entry. | |
48 // static | |
49 gfx::Insets NativeTextfieldGtk::GetEntryInnerBorder(GtkEntry* entry) { | |
50 const GtkBorder* inner_border = gtk_entry_get_inner_border(entry); | |
51 if (inner_border) | |
52 return gfx::Insets(*inner_border); | |
53 | |
54 // No explicit border set, try the style. | |
55 GtkBorder* style_border; | |
56 gtk_widget_style_get(GTK_WIDGET(entry), "inner-border", &style_border, NULL); | |
57 if (style_border) { | |
58 gfx::Insets insets = gfx::Insets(*style_border); | |
59 gtk_border_free(style_border); | |
60 return insets; | |
61 } | |
62 | |
63 // If border is null, Gtk uses 2 on all sides. | |
64 return gfx::Insets(2, 2, 2, 2); | |
65 } | |
66 | |
67 gfx::Insets NativeTextfieldGtk::GetTextViewInnerBorder(GtkTextView* text_view) { | |
68 return gfx::Insets(kTextViewBorderWidth / 2, kTextViewBorderWidth / 2, | |
69 kTextViewBorderWidth / 2, kTextViewBorderWidth / 2); | |
70 } | |
71 | |
72 //////////////////////////////////////////////////////////////////////////////// | |
73 // NativeTextfieldGtk, NativeTextfieldWrapper implementation: | |
74 | |
75 string16 NativeTextfieldGtk::GetText() const { | |
76 return UTF8ToUTF16(gtk_entry_get_text(GTK_ENTRY(native_view()))); | |
77 } | |
78 | |
79 void NativeTextfieldGtk::UpdateText() { | |
80 if (!native_view()) | |
81 return; | |
82 gtk_entry_set_text(GTK_ENTRY(native_view()), | |
83 UTF16ToUTF8(textfield_->text()).c_str()); | |
84 } | |
85 | |
86 void NativeTextfieldGtk::AppendText(const string16& text) { | |
87 if (!native_view()) | |
88 return; | |
89 gtk_entry_append_text(GTK_ENTRY(native_view()), UTF16ToUTF8(text).c_str()); | |
90 } | |
91 | |
92 string16 NativeTextfieldGtk::GetSelectedText() const { | |
93 if (!native_view()) | |
94 return string16(); | |
95 | |
96 string16 result; | |
97 | |
98 gint start_pos; | |
99 gint end_pos; | |
100 if (!gtk_editable_get_selection_bounds(GTK_EDITABLE(native_view()), | |
101 &start_pos, &end_pos)) | |
102 return result; // No selection. | |
103 | |
104 UTF8ToUTF16(gtk_editable_get_chars(GTK_EDITABLE(native_view()), | |
105 start_pos, end_pos), | |
106 end_pos - start_pos, &result); | |
107 | |
108 return result; | |
109 } | |
110 | |
111 void NativeTextfieldGtk::SelectAll() { | |
112 if (!native_view()) | |
113 return; | |
114 // -1 as the end position selects to the end of the text. | |
115 gtk_editable_select_region(GTK_EDITABLE(native_view()), 0, -1); | |
116 } | |
117 | |
118 void NativeTextfieldGtk::ClearSelection() { | |
119 if (!native_view()) | |
120 return; | |
121 gtk_editable_select_region(GTK_EDITABLE(native_view()), 0, 0); | |
122 } | |
123 | |
124 void NativeTextfieldGtk::UpdateBorder() { | |
125 if (!native_view()) | |
126 return; | |
127 | |
128 if (!textfield_->draw_border()) | |
129 gtk_entry_set_has_frame(GTK_ENTRY(native_view()), false); | |
130 } | |
131 | |
132 void NativeTextfieldGtk::UpdateTextColor() { | |
133 if (textfield_->use_default_text_color()) { | |
134 // Passing NULL as the color undoes the effect of previous calls to | |
135 // gtk_widget_modify_text. | |
136 gtk_widget_modify_text(native_view(), GTK_STATE_NORMAL, NULL); | |
137 return; | |
138 } | |
139 GdkColor gdk_color = gfx::SkColorToGdkColor(textfield_->text_color()); | |
140 gtk_widget_modify_text(native_view(), GTK_STATE_NORMAL, &gdk_color); | |
141 } | |
142 | |
143 void NativeTextfieldGtk::UpdateBackgroundColor() { | |
144 if (textfield_->use_default_background_color()) { | |
145 // Passing NULL as the color undoes the effect of previous calls to | |
146 // gtk_widget_modify_base. | |
147 gtk_widget_modify_base(native_view(), GTK_STATE_NORMAL, NULL); | |
148 return; | |
149 } | |
150 GdkColor gdk_color = gfx::SkColorToGdkColor(textfield_->background_color()); | |
151 gtk_widget_modify_base(native_view(), GTK_STATE_NORMAL, &gdk_color); | |
152 } | |
153 | |
154 void NativeTextfieldGtk::UpdateCursorColor() { | |
155 if (!textfield_->use_default_cursor_color()) | |
156 NOTIMPLEMENTED(); | |
157 } | |
158 | |
159 void NativeTextfieldGtk::UpdateReadOnly() { | |
160 if (!native_view()) | |
161 return; | |
162 gtk_editable_set_editable(GTK_EDITABLE(native_view()), | |
163 !textfield_->read_only()); | |
164 } | |
165 | |
166 void NativeTextfieldGtk::UpdateFont() { | |
167 if (!native_view()) | |
168 return; | |
169 PangoFontDescription* pfd = textfield_->font().GetNativeFont(); | |
170 gtk_widget_modify_font(native_view(), pfd); | |
171 pango_font_description_free(pfd); | |
172 } | |
173 | |
174 void NativeTextfieldGtk::UpdateIsObscured() { | |
175 if (!native_view()) | |
176 return; | |
177 gtk_entry_set_visibility(GTK_ENTRY(native_view()), !textfield_->IsObscured()); | |
178 } | |
179 | |
180 void NativeTextfieldGtk::UpdateEnabled() { | |
181 if (!native_view()) | |
182 return; | |
183 SetEnabled(textfield_->enabled()); | |
184 } | |
185 | |
186 gfx::Insets NativeTextfieldGtk::CalculateInsets() { | |
187 if (!native_view()) | |
188 return gfx::Insets(); | |
189 | |
190 GtkWidget* widget = native_view(); | |
191 gfx::Insets insets; | |
192 | |
193 GtkEntry* entry = GTK_ENTRY(widget); | |
194 insets += GetEntryInnerBorder(entry); | |
195 if (entry->has_frame) { | |
196 insets += gfx::Insets(widget->style->ythickness, | |
197 widget->style->xthickness, | |
198 widget->style->ythickness, | |
199 widget->style->xthickness); | |
200 } | |
201 | |
202 gboolean interior_focus; | |
203 gint focus_width; | |
204 gtk_widget_style_get(widget, | |
205 "focus-line-width", &focus_width, | |
206 "interior-focus", &interior_focus, | |
207 NULL); | |
208 if (!interior_focus) | |
209 insets += gfx::Insets(focus_width, focus_width, focus_width, focus_width); | |
210 | |
211 return insets; | |
212 } | |
213 | |
214 void NativeTextfieldGtk::UpdateHorizontalMargins() { | |
215 if (!native_view()) | |
216 return; | |
217 | |
218 int left, right; | |
219 if (!textfield_->GetHorizontalMargins(&left, &right)) | |
220 return; | |
221 | |
222 gfx::Insets insets = GetEntryInnerBorder(GTK_ENTRY(native_view())); | |
223 GtkBorder border = {left, right, insets.top(), insets.bottom()}; | |
224 gtk_entry_set_inner_border(GTK_ENTRY(native_view()), &border); | |
225 } | |
226 | |
227 void NativeTextfieldGtk::UpdateVerticalMargins() { | |
228 if (!native_view()) | |
229 return; | |
230 | |
231 int top, bottom; | |
232 if (!textfield_->GetVerticalMargins(&top, &bottom)) | |
233 return; | |
234 | |
235 gfx::Insets insets = GetEntryInnerBorder(GTK_ENTRY(native_view())); | |
236 GtkBorder border = {insets.left(), insets.right(), top, bottom}; | |
237 gtk_entry_set_inner_border(GTK_ENTRY(native_view()), &border); | |
238 } | |
239 | |
240 bool NativeTextfieldGtk::SetFocus() { | |
241 OnFocus(); | |
242 return true; | |
243 } | |
244 | |
245 View* NativeTextfieldGtk::GetView() { | |
246 return this; | |
247 } | |
248 | |
249 gfx::NativeView NativeTextfieldGtk::GetTestingHandle() const { | |
250 return native_view(); | |
251 } | |
252 | |
253 bool NativeTextfieldGtk::IsIMEComposing() const { | |
254 return false; | |
255 } | |
256 | |
257 void NativeTextfieldGtk::GetSelectedRange(ui::Range* range) const { | |
258 gint start_pos; | |
259 gint end_pos; | |
260 gtk_editable_get_selection_bounds( | |
261 GTK_EDITABLE(native_view()), &start_pos, &end_pos); | |
262 *range = ui::Range(start_pos, end_pos); | |
263 } | |
264 | |
265 void NativeTextfieldGtk::SelectRange(const ui::Range& range) { | |
266 NOTREACHED(); | |
267 } | |
268 | |
269 void NativeTextfieldGtk::GetSelectionModel(gfx::SelectionModel* sel) const { | |
270 NOTREACHED(); | |
271 } | |
272 | |
273 void NativeTextfieldGtk::SelectSelectionModel(const gfx::SelectionModel& sel) { | |
274 NOTREACHED(); | |
275 } | |
276 | |
277 size_t NativeTextfieldGtk::GetCursorPosition() const { | |
278 NOTREACHED(); | |
279 return 0U; | |
280 } | |
281 | |
282 bool NativeTextfieldGtk::HandleKeyPressed(const views::KeyEvent& e) { | |
283 return false; | |
284 } | |
285 | |
286 bool NativeTextfieldGtk::HandleKeyReleased(const views::KeyEvent& e) { | |
287 return false; | |
288 } | |
289 | |
290 void NativeTextfieldGtk::HandleFocus() { | |
291 } | |
292 | |
293 void NativeTextfieldGtk::HandleBlur() { | |
294 } | |
295 | |
296 ui::TextInputClient* NativeTextfieldGtk::GetTextInputClient() { | |
297 return NULL; | |
298 } | |
299 | |
300 void NativeTextfieldGtk::ApplyStyleRange(const gfx::StyleRange& style) { | |
301 NOTREACHED(); | |
302 } | |
303 | |
304 void NativeTextfieldGtk::ApplyDefaultStyle() { | |
305 NOTREACHED(); | |
306 } | |
307 | |
308 void NativeTextfieldGtk::ClearEditHistory() { | |
309 NOTREACHED(); | |
310 } | |
311 | |
312 int NativeTextfieldGtk::GetFontHeight() { | |
313 return textfield_->font().GetHeight(); | |
314 } | |
315 | |
316 void NativeTextfieldGtk::OnActivate(GtkWidget* native_widget) { | |
317 GdkEvent* event = gtk_get_current_event(); | |
318 if (!event || event->type != GDK_KEY_PRESS) | |
319 return; | |
320 | |
321 KeyEvent views_key_event(event); | |
322 gboolean handled = false; | |
323 | |
324 TextfieldController* controller = textfield_->GetController(); | |
325 if (controller) | |
326 handled = controller->HandleKeyEvent(textfield_, views_key_event); | |
327 | |
328 Widget* widget = GetWidget(); | |
329 if (!handled && widget) { | |
330 NativeWidgetGtk* native_widget = | |
331 static_cast<NativeWidgetGtk*>(widget->native_widget()); | |
332 handled = native_widget->HandleKeyboardEvent(views_key_event); | |
333 } | |
334 | |
335 // Stop signal emission if the key event is handled by us. | |
336 if (handled) { | |
337 // Only GtkEntry has "activate" signal. | |
338 static guint signal_id = g_signal_lookup("activate", GTK_TYPE_ENTRY); | |
339 g_signal_stop_emission(native_widget, signal_id, 0); | |
340 } | |
341 } | |
342 | |
343 void NativeTextfieldGtk::OnChanged(GObject* object) { | |
344 // We need to call TextfieldController::ContentsChanged() explicitly if the | |
345 // paste action didn't change the content at all. See http://crbug.com/79002 | |
346 const bool call_contents_changed = | |
347 paste_clipboard_requested_ && GetText() == textfield_->text(); | |
348 textfield_->SyncText(); | |
349 textfield_->GetWidget()->NotifyAccessibilityEvent( | |
350 textfield_, ui::AccessibilityTypes::EVENT_TEXT_CHANGED, true); | |
351 if (call_contents_changed) { | |
352 TextfieldController* controller = textfield_->GetController(); | |
353 if (controller) | |
354 controller->ContentsChanged(textfield_, textfield_->text()); | |
355 } | |
356 paste_clipboard_requested_ = false; | |
357 } | |
358 | |
359 gboolean NativeTextfieldGtk::OnButtonPressEvent(GtkWidget* widget, | |
360 GdkEventButton* event) { | |
361 paste_clipboard_requested_ = false; | |
362 return false; | |
363 } | |
364 | |
365 gboolean NativeTextfieldGtk::OnButtonReleaseEventAfter(GtkWidget* widget, | |
366 GdkEventButton* event) { | |
367 textfield_->GetWidget()->NotifyAccessibilityEvent( | |
368 textfield_, ui::AccessibilityTypes::EVENT_TEXT_CHANGED, true); | |
369 return false; | |
370 } | |
371 | |
372 gboolean NativeTextfieldGtk::OnKeyPressEvent(GtkWidget* widget, | |
373 GdkEventKey* event) { | |
374 paste_clipboard_requested_ = false; | |
375 return false; | |
376 } | |
377 | |
378 gboolean NativeTextfieldGtk::OnKeyPressEventAfter(GtkWidget* widget, | |
379 GdkEventKey* event) { | |
380 TextfieldController* controller = textfield_->GetController(); | |
381 if (controller) { | |
382 KeyEvent key_event(reinterpret_cast<GdkEvent*>(event)); | |
383 return controller->HandleKeyEvent(textfield_, key_event); | |
384 } | |
385 return false; | |
386 } | |
387 | |
388 void NativeTextfieldGtk::OnMoveCursor(GtkWidget* widget, | |
389 GtkMovementStep step, | |
390 gint count, | |
391 gboolean extend_selection) { | |
392 textfield_->GetWidget()->NotifyAccessibilityEvent( | |
393 textfield_, ui::AccessibilityTypes::EVENT_TEXT_CHANGED, true); | |
394 } | |
395 | |
396 void NativeTextfieldGtk::OnPasteClipboard(GtkWidget* widget) { | |
397 if (!textfield_->read_only()) | |
398 paste_clipboard_requested_ = true; | |
399 } | |
400 | |
401 //////////////////////////////////////////////////////////////////////////////// | |
402 // NativeTextfieldGtk, NativeControlGtk overrides: | |
403 | |
404 void NativeTextfieldGtk::CreateNativeControl() { | |
405 NativeControlCreated(gtk_views_entry_new(this)); | |
406 gtk_entry_set_invisible_char(GTK_ENTRY(native_view()), | |
407 static_cast<gunichar>(kObscuredChar)); | |
408 textfield_->UpdateAllProperties(); | |
409 } | |
410 | |
411 void NativeTextfieldGtk::NativeControlCreated(GtkWidget* widget) { | |
412 NativeControlGtk::NativeControlCreated(widget); | |
413 | |
414 g_signal_connect(widget, "changed", G_CALLBACK(OnChangedThunk), this); | |
415 // In order to properly trigger Accelerators bound to VKEY_RETURN, we need | |
416 // to send an event when the widget gets the activate signal. | |
417 g_signal_connect(widget, "activate", G_CALLBACK(OnActivateThunk), this); | |
418 g_signal_connect(widget, "move-cursor", G_CALLBACK(OnMoveCursorThunk), this); | |
419 g_signal_connect(widget, "button-press-event", | |
420 G_CALLBACK(OnButtonPressEventThunk), this); | |
421 g_signal_connect(widget, "key-press-event", | |
422 G_CALLBACK(OnKeyPressEventThunk), this); | |
423 g_signal_connect(widget, "paste-clipboard", | |
424 G_CALLBACK(OnPasteClipboardThunk), this); | |
425 | |
426 g_signal_connect_after(widget, "button-release-event", | |
427 G_CALLBACK(OnButtonReleaseEventAfterThunk), this); | |
428 g_signal_connect_after(widget, "key-press-event", | |
429 G_CALLBACK(OnKeyPressEventAfterThunk), this); | |
430 } | |
431 | |
432 bool NativeTextfieldGtk::IsObscured() { | |
433 return textfield_->IsObscured(); | |
434 } | |
435 | |
436 /////////////////////////////////////////////////////////////////////////////// | |
437 // NativeTextfieldWrapper: | |
438 | |
439 // static | |
440 NativeTextfieldWrapper* NativeTextfieldWrapper::CreateWrapper( | |
441 Textfield* field) { | |
442 if (Widget::IsPureViews()) | |
443 return new NativeTextfieldViews(field); | |
444 return new NativeTextfieldGtk(field); | |
445 } | |
446 | |
447 } // namespace views | |
OLD | NEW |