Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(359)

Side by Side Diff: chrome/browser/autocomplete/autocomplete_edit_view_gtk.cc

Issue 40013: Implement a GTK LocationBarView and Autocomplete. (Closed)
Patch Set: Feedback Created 11 years, 9 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(Empty)
1 // Copyright (c) 2009 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 "chrome/browser/autocomplete/autocomplete_edit_view_gtk.h"
6
7 #include <gtk/gtk.h>
8
9 #include "base/logging.h"
10 #include "base/string_util.h"
11 #include "chrome/browser/autocomplete/autocomplete_edit.h"
12 #include "chrome/browser/autocomplete/autocomplete_popup_model.h"
13 #include "chrome/browser/autocomplete/autocomplete_popup_view_gtk.h"
14 #include "chrome/browser/tab_contents/tab_contents.h"
15 #include "chrome/browser/toolbar_model.h"
16 #include "chrome/common/notification_service.h"
17 #include "googleurl/src/gurl.h"
18
19 namespace {
20
21 const char kTextBaseColor[] = "#808080";
22 const char kSecureSchemeColor[] = "#009614";
23 const char kInsecureSchemeColor[] = "#009614";
24 const GdkColor kSecureBackgroundColor = {0, 65535, 62965, 50115}; // #fff5c3
25 const GdkColor kInsecureBackgroundColor = {0, 65535, 65535, 65535}; // #ffffff
26
27 } // namespace
28
29 AutocompleteEditViewGtk::AutocompleteEditViewGtk(
30 AutocompleteEditController* controller,
31 ToolbarModel* toolbar_model,
32 Profile* profile,
33 CommandUpdater* command_updater)
34 : text_view_(NULL),
35 tag_table_(NULL),
36 text_buffer_(NULL),
37 base_tag_(NULL),
38 secure_scheme_tag_(NULL),
39 insecure_scheme_tag_(NULL),
40 model_(new AutocompleteEditModel(this, controller, profile)),
41 popup_view_(new AutocompletePopupViewGtk(this, model_.get(), profile)),
42 controller_(controller),
43 toolbar_model_(toolbar_model),
44 command_updater_(command_updater),
45 popup_window_mode_(false), // TODO(deanm)
46 scheme_security_level_(ToolbarModel::NORMAL) {
47 model_->set_popup_model(popup_view_->model());
48 }
49
50 AutocompleteEditViewGtk::~AutocompleteEditViewGtk() {
51 NotificationService::current()->Notify(
52 NotificationType::AUTOCOMPLETE_EDIT_DESTROYED,
53 Source<AutocompleteEditViewGtk>(this),
54 NotificationService::NoDetails());
55 }
56
57 void AutocompleteEditViewGtk::Init() {
58 tag_table_ = gtk_text_tag_table_new();
59 text_buffer_ = gtk_text_buffer_new(tag_table_);
60 text_view_ = gtk_text_view_new_with_buffer(text_buffer_);
61
62 gtk_text_view_set_left_margin(GTK_TEXT_VIEW(text_view_), 4);
63 gtk_text_view_set_right_margin(GTK_TEXT_VIEW(text_view_), 4);
64
65 // TODO(deanm): This is a super lame attempt to vertically center our single
66 // line of text in a multiline edit control. Mannnn.
67 gtk_text_view_set_pixels_above_lines(GTK_TEXT_VIEW(text_view_), 4);
68
69 // TODO(deanm): This will probably have to be handled differently with the
70 // tab to search business. Maybe we should just eat the tab characters.
71 // We want the tab key to move focus, not insert a tab.
72 gtk_text_view_set_accepts_tab(GTK_TEXT_VIEW(text_view_), false);
73
74 base_tag_ = gtk_text_buffer_create_tag(text_buffer_,
75 NULL, "foreground", kTextBaseColor, NULL);
76 secure_scheme_tag_ = gtk_text_buffer_create_tag(text_buffer_,
77 NULL, "foreground", kSecureSchemeColor, NULL);
78 insecure_scheme_tag_ = gtk_text_buffer_create_tag(text_buffer_,
79 NULL, "foreground", kInsecureSchemeColor, NULL);
80
81 // NOTE: This code used to connect to "changed", however this was fired too
82 // often and during bad times (our own buffer changes?). It works out much
83 // better to listen to end-user-action, which should be fired whenever the
84 // user makes some sort of change to the buffer.
85 g_signal_connect(text_buffer_, "begin-user-action",
86 G_CALLBACK(&HandleBeginUserActionThunk), this);
87 g_signal_connect(text_buffer_, "end-user-action",
88 G_CALLBACK(&HandleEndUserActionThunk), this);
89 g_signal_connect(text_view_, "size-request",
90 G_CALLBACK(&HandleViewSizeRequest), this);
91 g_signal_connect(text_view_, "button-press-event",
92 G_CALLBACK(&HandleViewButtonPressThunk), this);
93 g_signal_connect(text_view_, "focus-in-event",
94 G_CALLBACK(&HandleViewFocusInThunk), this);
95 g_signal_connect(text_view_, "focus-out-event",
96 G_CALLBACK(&HandleViewFocusOutThunk), this);
97 // NOTE: The GtkTextView documentation asks you not to connect to this
98 // signal, but it is very convenient and clean for catching up/down.
99 g_signal_connect(text_view_, "move-cursor",
100 G_CALLBACK(&HandleViewMoveCursorThunk), this);
101 }
102
103 void AutocompleteEditViewGtk::FocusLocation() {
104 gtk_widget_grab_focus(text_view_);
105 SelectAll(false);
106 }
107
108 void AutocompleteEditViewGtk::SaveStateToTab(TabContents* tab) {
109 NOTIMPLEMENTED();
110 }
111
112 void AutocompleteEditViewGtk::Update(const TabContents* contents) {
113 // NOTE: We're getting the URL text here from the ToolbarModel.
114 bool visibly_changed_permanent_text =
115 model_->UpdatePermanentText(toolbar_model_->GetText());
116
117 ToolbarModel::SecurityLevel security_level =
118 toolbar_model_->GetSchemeSecurityLevel();
119 bool changed_security_level = (security_level != scheme_security_level_);
120 scheme_security_level_ = security_level;
121
122 if (contents) {
123 RevertAll();
124 // TODO(deanm): Tab switching. The Windows code puts some state in a
125 // PropertyBag on the tab contents, and restores state from there.
126 } else if (visibly_changed_permanent_text) {
127 RevertAll();
128 // TODO(deanm): There should be code to restore select all here.
129 } else if(changed_security_level) {
130 EmphasizeURLComponents();
131 }
132 }
133
134 void AutocompleteEditViewGtk::OpenURL(const GURL& url,
135 WindowOpenDisposition disposition,
136 PageTransition::Type transition,
137 const GURL& alternate_nav_url,
138 size_t selected_line,
139 const std::wstring& keyword) {
140 if (!url.is_valid())
141 return;
142
143 model_->SendOpenNotification(selected_line, keyword);
144
145 if (disposition != NEW_BACKGROUND_TAB)
146 RevertAll(); // Revert the box to its unedited state
147 controller_->OnAutocompleteAccept(url, disposition, transition,
148 alternate_nav_url);
149 }
150
151 std::wstring AutocompleteEditViewGtk::GetText() const {
152 GtkTextIter start, end;
153 gtk_text_buffer_get_bounds(text_buffer_, &start, &end);
154 gchar* utf8 = gtk_text_buffer_get_text(text_buffer_, &start, &end, false);
155 std::wstring out(UTF8ToWide(utf8));
156 g_free(utf8);
157 return out;
158 }
159
160 void AutocompleteEditViewGtk::SetUserText(const std::wstring& text,
161 const std::wstring& display_text,
162 bool update_popup) {
163 NOTIMPLEMENTED();
164 }
165
166 void AutocompleteEditViewGtk::SetWindowTextAndCaretPos(const std::wstring& text,
167 size_t caret_pos) {
168 std::string utf8 = WideToUTF8(text);
169 gtk_text_buffer_set_text(text_buffer_, utf8.data(), utf8.length());
170 EmphasizeURLComponents();
171
172 GtkTextIter cur_pos;
173 gtk_text_buffer_get_iter_at_offset(text_buffer_, &cur_pos, caret_pos);
174 gtk_text_buffer_place_cursor(text_buffer_, &cur_pos);
175 }
176
177 bool AutocompleteEditViewGtk::IsSelectAll() {
178 NOTIMPLEMENTED();
179 return false;
180 }
181
182 void AutocompleteEditViewGtk::SelectAll(bool reversed) {
183 GtkTextIter start, end;
184 if (reversed) {
185 gtk_text_buffer_get_bounds(text_buffer_, &end, &start);
186 } else {
187 gtk_text_buffer_get_bounds(text_buffer_, &start, &end);
188 }
189 gtk_text_buffer_place_cursor(text_buffer_, &start);
190 gtk_text_buffer_select_range(text_buffer_, &start, &end);
191 }
192
193 void AutocompleteEditViewGtk::RevertAll() {
194 ClosePopup();
195 model_->Revert();
196 TextChanged();
197 }
198
199 void AutocompleteEditViewGtk::UpdatePopup() {
200 model_->SetInputInProgress(true);
201 if (!model_->has_focus())
202 return;
203
204 // Don't inline autocomplete when the caret/selection isn't at the end of
205 // the text.
206 CharRange sel = GetSelection();
207 model_->StartAutocomplete(sel.cp_max < GetTextLength());
208 }
209
210 void AutocompleteEditViewGtk::ClosePopup() {
211 popup_view_->model()->StopAutocomplete();
212 }
213
214 void AutocompleteEditViewGtk::OnTemporaryTextMaybeChanged(
215 const std::wstring& display_text,
216 bool save_original_selection) {
217 // TODO(deanm): Ignoring save_original_selection here, etc.
218 SetWindowTextAndCaretPos(display_text, display_text.length());
219 TextChanged();
220 }
221
222 bool AutocompleteEditViewGtk::OnInlineAutocompleteTextMaybeChanged(
223 const std::wstring& display_text,
224 size_t user_text_length) {
225 if (display_text == GetText())
226 return false;
227
228 SetWindowTextAndCaretPos(display_text, 0);
229
230 // Select the part of the text that was inline autocompleted.
231 GtkTextIter bound, insert;
232 gtk_text_buffer_get_bounds(text_buffer_, &insert, &bound);
233 gtk_text_buffer_get_iter_at_offset(text_buffer_, &insert, user_text_length);
234 gtk_text_buffer_select_range(text_buffer_, &insert, &bound);
235
236 TextChanged();
237 return true;
238 }
239
240 void AutocompleteEditViewGtk::OnRevertTemporaryText() {
241 NOTIMPLEMENTED();
242 }
243
244 void AutocompleteEditViewGtk::OnBeforePossibleChange() {
245 // Record our state.
246 text_before_change_ = GetText();
247 sel_before_change_ = GetSelection();
248 }
249
250 // TODO(deanm): This is mostly stolen from Windows, and will need some work.
251 bool AutocompleteEditViewGtk::OnAfterPossibleChange() {
252 CharRange new_sel = GetSelection();
253 int length = GetTextLength();
254 bool selection_differs = (new_sel.cp_min != sel_before_change_.cp_min) ||
255 (new_sel.cp_max != sel_before_change_.cp_max);
256 bool at_end_of_edit = (new_sel.cp_min == length && new_sel.cp_max == length);
257
258 // See if the text or selection have changed since OnBeforePossibleChange().
259 std::wstring new_text(GetText());
260 bool text_differs = (new_text != text_before_change_);
261
262 // When the user has deleted text, we don't allow inline autocomplete. Make
263 // sure to not flag cases like selecting part of the text and then pasting
264 // (or typing) the prefix of that selection. (We detect these by making
265 // sure the caret, which should be after any insertion, hasn't moved
266 // forward of the old selection start.)
267 bool just_deleted_text =
268 (text_before_change_.length() > new_text.length()) &&
269 (new_sel.cp_min <= std::min(sel_before_change_.cp_min,
270 sel_before_change_.cp_max));
271
272 bool something_changed = model_->OnAfterPossibleChange(new_text,
273 selection_differs, text_differs, just_deleted_text, at_end_of_edit);
274
275 if (something_changed && text_differs)
276 TextChanged();
277
278 return something_changed;
279 }
280
281 void AutocompleteEditViewGtk::BottomLeftPosWidth(int* x, int* y, int* width) {
282 gdk_window_get_origin(text_view_->window, x, y);
283 *y += text_view_->allocation.height;
284 *width = text_view_->allocation.width;
285 }
286
287 void AutocompleteEditViewGtk::HandleBeginUserAction() {
288 OnBeforePossibleChange();
289 }
290
291 void AutocompleteEditViewGtk::HandleEndUserAction() {
292 bool had_newline = false;
293
294 // TODO(deanm): obviously super inefficient.
295 for(;;) {
296 GtkTextIter cur, end;
297 gtk_text_buffer_get_bounds(text_buffer_, &cur, &end);
298
299 while (!gtk_text_iter_equal(&cur, &end)) {
300 if (gtk_text_iter_ends_line(&cur)) {
301 had_newline = true;
302 GtkTextIter next = cur;
303 gtk_text_iter_forward_char(&next);
304 gtk_text_buffer_delete(text_buffer_, &cur, &next);
305
306 // We've invalidated our iterators, gotta start again.
307 break;
308 }
309
310 gtk_text_iter_forward_char(&cur);
311 }
312
313 // We've exhausted the whole input and there is now only 1 line, good.
314 if (gtk_text_iter_equal(&cur, &end))
315 break;
316 }
317
318 OnAfterPossibleChange();
319
320 if (had_newline)
321 model_->AcceptInput(CURRENT_TAB, false);
322 }
323
324 // static
325 void AutocompleteEditViewGtk::HandleViewSizeRequest(GtkWidget* view,
326 GtkRequisition* req,
327 gpointer unused) {
328 // Don't force a minimum size, allow our embedder to size us better.
329 req->height = req->width = 1;
330 }
331
332 gboolean AutocompleteEditViewGtk::HandleViewButtonPress(GdkEventButton* event) {
333 // When the GtkTextView is clicked, it will call gtk_widget_grab_focus.
334 // I believe this causes the focus-in event to be fired before the main
335 // clicked handling code. If we were to try to set the selection from
336 // the focus-in event, it's just going to be undone by the click handler.
337 // This is a bit ugly. We shim in to get the click before the GtkTextView,
338 // then if we don't have focus, we (hopefully safely) assume that the click
339 // will cause us to become focused. We call GtkTextView's default handler
340 // and then stop propagation. This allows us to run our code after the
341 // default handler, even if that handler stopped propagation.
342 if (GTK_WIDGET_HAS_FOCUS(text_view_))
343 return FALSE; // Continue to propagate into the GtkTextView handler.
344
345 // Call the GtkTextView default handler, ignoring the fact that it will
346 // likely have told us to stop propagating. We want to handle selection.
347 GtkWidgetClass* klass = GTK_WIDGET_GET_CLASS(text_view_);
348 klass->button_press_event(text_view_, event);
349
350 // Select the full input when we get focus.
351 SelectAll(false);
352 // So we told the buffer where the cursor should be, but make sure to tell
353 // the view so it can scroll it to be visible if needed.
354 // NOTE: This function doesn't seem to like a count of 0, looking at the
355 // code it will skip an important loop. Use -1 to achieve the same.
356 GtkTextIter start, end;
357 gtk_text_buffer_get_bounds(text_buffer_, &start, &end);
358 gtk_text_view_move_visually(GTK_TEXT_VIEW(text_view_), &start, -1);
359
360 return TRUE; // Don't continue, we called the default handler already.
361 }
362
363 gboolean AutocompleteEditViewGtk::HandleViewFocusIn() {
364 model_->OnSetFocus(false);
365 // TODO(deanm): Some keyword hit business, etc here.
366
367 return FALSE; // Continue propagation.
368 }
369
370 gboolean AutocompleteEditViewGtk::HandleViewFocusOut() {
371 // Close the popup.
372 ClosePopup();
373
374 // Tell the model to reset itself.
375 model_->OnKillFocus();
376
377 // TODO(deanm): This probably isn't right, and doesn't match Windows. We
378 // don't really want to match Windows though, because it probably feels
379 // wrong on Linux. Firefox doesn't have great behavior here also, imo.
380 // Deselect any selection and make sure the input is at the beginning.
381 GtkTextIter start, end;
382 gtk_text_buffer_get_bounds(text_buffer_, &start, &end);
383 gtk_text_buffer_place_cursor(text_buffer_, &start);
384 return FALSE; // Pass the event on to the GtkTextView.
385 }
386
387 void AutocompleteEditViewGtk::HandleViewMoveCursor(
388 GtkMovementStep step,
389 gint count,
390 gboolean extendion_selection) {
391 // Handle up / down cursor movement on our own.
392 if (step == GTK_MOVEMENT_DISPLAY_LINES) {
393 model_->OnUpOrDownKeyPressed(count);
394 // move-cursor doesn't use a signal accumulator on the return value (it
395 // just ignores them), so we have to stop the propagation.
396 g_signal_stop_emission_by_name(text_view_, "move-cursor");
397 return;
398 }
399 // Propagate into GtkTextView.
400 }
401
402 AutocompleteEditViewGtk::CharRange AutocompleteEditViewGtk::GetSelection() {
403 // You can not just use get_selection_bounds here, since the order will be
404 // ascending, and you don't know where the user's start and end of the
405 // selection was (if the selection was forwards or backwards). Get the
406 // actual marks so that we can preserve the selection direction.
407 GtkTextIter start, insert;
408 GtkTextMark* mark;
409
410 mark = gtk_text_buffer_get_selection_bound(text_buffer_);
411 gtk_text_buffer_get_iter_at_mark(text_buffer_, &start, mark);
412
413 mark = gtk_text_buffer_get_insert(text_buffer_);
414 gtk_text_buffer_get_iter_at_mark(text_buffer_, &insert, mark);
415
416 return CharRange(gtk_text_iter_get_offset(&start),
417 gtk_text_iter_get_offset(&insert));
418 }
419
420 void AutocompleteEditViewGtk::ItersFromCharRange(const CharRange& range,
421 GtkTextIter* iter_min,
422 GtkTextIter* iter_max) {
423 gtk_text_buffer_get_iter_at_offset(text_buffer_, iter_min, range.cp_min);
424 gtk_text_buffer_get_iter_at_offset(text_buffer_, iter_max, range.cp_max);
425 }
426
427 int AutocompleteEditViewGtk::GetTextLength() {
428 GtkTextIter start, end;
429 gtk_text_buffer_get_bounds(text_buffer_, &start, &end);
430 return gtk_text_iter_get_offset(&end);
431 }
432
433 void AutocompleteEditViewGtk::EmphasizeURLComponents() {
434 // See whether the contents are a URL with a non-empty host portion, which we
435 // should emphasize. To check for a URL, rather than using the type returned
436 // by Parse(), ask the model, which will check the desired page transition for
437 // this input. This can tell us whether an UNKNOWN input string is going to
438 // be treated as a search or a navigation, and is the same method the Paste
439 // And Go system uses.
440 url_parse::Parsed parts;
441 AutocompleteInput::Parse(GetText(), model_->GetDesiredTLD(), &parts, NULL);
442 bool emphasize = model_->CurrentTextIsURL() && (parts.host.len > 0);
443
444 // Set the baseline emphasis.
445 GtkTextIter start, end;
446 gtk_text_buffer_get_bounds(text_buffer_, &start, &end);
447 gtk_text_buffer_remove_all_tags(text_buffer_, &start, &end);
448 if (emphasize) {
449 gtk_text_buffer_apply_tag(text_buffer_, base_tag_, &start, &end);
450
451 // We've found a host name, give it more emphasis.
452 gtk_text_buffer_get_iter_at_line_index(text_buffer_, &start, 0,
453 parts.host.begin);
454 gtk_text_buffer_get_iter_at_line_index(text_buffer_, &end, 0,
455 parts.host.end());
456 gtk_text_buffer_remove_all_tags(text_buffer_, &start, &end);
457 }
458
459 // Emphasize the scheme for security UI display purposes (if necessary).
460 if (!model_->user_input_in_progress() && parts.scheme.is_nonempty() &&
461 (scheme_security_level_ != ToolbarModel::NORMAL)) {
462 gtk_text_buffer_get_iter_at_line_index(text_buffer_, &start, 0,
463 parts.scheme.begin);
464 gtk_text_buffer_get_iter_at_line_index(text_buffer_, &end, 0,
465 parts.scheme.end());
466 const GdkColor* background;
467 if (scheme_security_level_ == ToolbarModel::SECURE) {
468 background = &kSecureBackgroundColor;
469 gtk_text_buffer_apply_tag(text_buffer_, secure_scheme_tag_,
470 &start, &end);
471 } else {
472 background = &kInsecureBackgroundColor;
473 gtk_text_buffer_apply_tag(text_buffer_, insecure_scheme_tag_,
474 &start, &end);
475 }
476 gtk_widget_modify_base(text_view_, GTK_STATE_NORMAL, background);
477 }
478 }
479
480 void AutocompleteEditViewGtk::TextChanged() {
481 EmphasizeURLComponents();
482 controller_->OnChanged();
483 }
OLDNEW
« no previous file with comments | « chrome/browser/autocomplete/autocomplete_edit_view_gtk.h ('k') | chrome/browser/autocomplete/autocomplete_popup_view_gtk.h » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698