OLD | NEW |
1 // Copyright (c) 2009 The Chromium Authors. All rights reserved. | 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 | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | 4 |
5 #include "chrome/browser/autocomplete/autocomplete_edit_view_gtk.h" | 5 #include "chrome/browser/autocomplete/autocomplete_edit_view_gtk.h" |
6 | 6 |
7 #include <gtk/gtk.h> | 7 #include <gtk/gtk.h> |
8 #include <gdk/gdkkeysyms.h> | 8 #include <gdk/gdkkeysyms.h> |
9 | 9 |
10 #include "app/l10n_util.h" | 10 #include "app/l10n_util.h" |
(...skipping 16 matching lines...) Expand all Loading... |
27 namespace { | 27 namespace { |
28 | 28 |
29 const char kTextBaseColor[] = "#808080"; | 29 const char kTextBaseColor[] = "#808080"; |
30 const char kSecureSchemeColor[] = "#009614"; | 30 const char kSecureSchemeColor[] = "#009614"; |
31 const char kInsecureSchemeColor[] = "#c80000"; | 31 const char kInsecureSchemeColor[] = "#c80000"; |
32 | 32 |
33 size_t GetUTF8Offset(const std::wstring& wide_text, size_t wide_text_offset) { | 33 size_t GetUTF8Offset(const std::wstring& wide_text, size_t wide_text_offset) { |
34 return WideToUTF8(wide_text.substr(0, wide_text_offset)).size(); | 34 return WideToUTF8(wide_text.substr(0, wide_text_offset)).size(); |
35 } | 35 } |
36 | 36 |
| 37 // Stores GTK+-specific state so it can be restored after switching tabs. |
| 38 struct ViewState { |
| 39 explicit ViewState(const AutocompleteEditViewGtk::CharRange& selection_range) |
| 40 : selection_range(selection_range) { |
| 41 } |
| 42 |
| 43 // Range of selected text. |
| 44 AutocompleteEditViewGtk::CharRange selection_range; |
| 45 }; |
| 46 |
| 47 struct AutocompleteEditState { |
| 48 AutocompleteEditState(const AutocompleteEditModel::State& model_state, |
| 49 const ViewState& view_state) |
| 50 : model_state(model_state), |
| 51 view_state(view_state) { |
| 52 } |
| 53 |
| 54 const AutocompleteEditModel::State model_state; |
| 55 const ViewState view_state; |
| 56 }; |
| 57 |
| 58 // Returns a lazily initialized property bag accessor for saving our state in a |
| 59 // TabContents. |
| 60 PropertyAccessor<AutocompleteEditState>* GetStateAccessor() { |
| 61 static PropertyAccessor<AutocompleteEditState> state; |
| 62 return &state; |
| 63 } |
| 64 |
37 } // namespace | 65 } // namespace |
38 | 66 |
39 AutocompleteEditViewGtk::AutocompleteEditViewGtk( | 67 AutocompleteEditViewGtk::AutocompleteEditViewGtk( |
40 AutocompleteEditController* controller, | 68 AutocompleteEditController* controller, |
41 ToolbarModel* toolbar_model, | 69 ToolbarModel* toolbar_model, |
42 Profile* profile, | 70 Profile* profile, |
43 CommandUpdater* command_updater, | 71 CommandUpdater* command_updater, |
44 AutocompletePopupPositioner* popup_positioner) | 72 AutocompletePopupPositioner* popup_positioner) |
45 : text_view_(NULL), | 73 : text_view_(NULL), |
46 tag_table_(NULL), | 74 tag_table_(NULL), |
47 text_buffer_(NULL), | 75 text_buffer_(NULL), |
48 base_tag_(NULL), | 76 base_tag_(NULL), |
49 secure_scheme_tag_(NULL), | 77 secure_scheme_tag_(NULL), |
50 insecure_scheme_tag_(NULL), | 78 insecure_scheme_tag_(NULL), |
51 primary_clipboard_(NULL), | |
52 model_(new AutocompleteEditModel(this, controller, profile)), | 79 model_(new AutocompleteEditModel(this, controller, profile)), |
53 popup_view_(new AutocompletePopupViewGtk(this, model_.get(), profile, | 80 popup_view_(new AutocompletePopupViewGtk(this, model_.get(), profile, |
54 popup_positioner)), | 81 popup_positioner)), |
55 controller_(controller), | 82 controller_(controller), |
56 toolbar_model_(toolbar_model), | 83 toolbar_model_(toolbar_model), |
57 command_updater_(command_updater), | 84 command_updater_(command_updater), |
58 popup_window_mode_(false), // TODO(deanm) | 85 popup_window_mode_(false), // TODO(deanm) |
59 scheme_security_level_(ToolbarModel::NORMAL) { | 86 scheme_security_level_(ToolbarModel::NORMAL), |
| 87 selection_saved_(false) { |
60 model_->set_popup_model(popup_view_->GetModel()); | 88 model_->set_popup_model(popup_view_->GetModel()); |
61 } | 89 } |
62 | 90 |
63 AutocompleteEditViewGtk::~AutocompleteEditViewGtk() { | 91 AutocompleteEditViewGtk::~AutocompleteEditViewGtk() { |
64 NotificationService::current()->Notify( | 92 NotificationService::current()->Notify( |
65 NotificationType::AUTOCOMPLETE_EDIT_DESTROYED, | 93 NotificationType::AUTOCOMPLETE_EDIT_DESTROYED, |
66 Source<AutocompleteEditViewGtk>(this), | 94 Source<AutocompleteEditViewGtk>(this), |
67 NotificationService::NoDetails()); | 95 NotificationService::NoDetails()); |
68 | 96 |
69 // Explicitly teardown members which have a reference to us. Just to be safe | 97 // Explicitly teardown members which have a reference to us. Just to be safe |
(...skipping 68 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
138 // signal, but it is very convenient and clean for catching up/down. | 166 // signal, but it is very convenient and clean for catching up/down. |
139 g_signal_connect(text_view_, "move-cursor", | 167 g_signal_connect(text_view_, "move-cursor", |
140 G_CALLBACK(&HandleViewMoveCursorThunk), this); | 168 G_CALLBACK(&HandleViewMoveCursorThunk), this); |
141 // Override the size request. We want to keep the original height request | 169 // Override the size request. We want to keep the original height request |
142 // from the widget, since that's font dependent. We want to ignore the width | 170 // from the widget, since that's font dependent. We want to ignore the width |
143 // so we don't force a minimum width based on the text length. | 171 // so we don't force a minimum width based on the text length. |
144 g_signal_connect(text_view_, "size-request", | 172 g_signal_connect(text_view_, "size-request", |
145 G_CALLBACK(&HandleViewSizeRequestThunk), this); | 173 G_CALLBACK(&HandleViewSizeRequestThunk), this); |
146 g_signal_connect(text_view_, "populate-popup", | 174 g_signal_connect(text_view_, "populate-popup", |
147 G_CALLBACK(&HandlePopulatePopupThunk), this); | 175 G_CALLBACK(&HandlePopulatePopupThunk), this); |
| 176 g_signal_connect(text_buffer_, "mark-set", |
| 177 G_CALLBACK(&HandleMarkSetThunk), this); |
148 } | 178 } |
149 | 179 |
150 void AutocompleteEditViewGtk::SetFocus() { | 180 void AutocompleteEditViewGtk::SetFocus() { |
151 gtk_widget_grab_focus(text_view_); | 181 gtk_widget_grab_focus(text_view_); |
152 } | 182 } |
153 | 183 |
154 void AutocompleteEditViewGtk::SaveStateToTab(TabContents* tab) { | 184 void AutocompleteEditViewGtk::SaveStateToTab(TabContents* tab) { |
155 NOTIMPLEMENTED(); | 185 DCHECK(tab); |
| 186 GetStateAccessor()->SetProperty( |
| 187 tab->property_bag(), |
| 188 AutocompleteEditState(model_->GetStateForTabSwitch(), |
| 189 ViewState(GetSelection()))); |
| 190 |
| 191 // If any text has been selected, register it as the PRIMARY selection so it |
| 192 // can still be pasted via middle-click after the text view is cleared. |
| 193 if (!selected_text_.empty() && !selection_saved_) { |
| 194 SavePrimarySelection(selected_text_); |
| 195 selection_saved_ = true; |
| 196 } |
156 } | 197 } |
157 | 198 |
158 void AutocompleteEditViewGtk::Update(const TabContents* contents) { | 199 void AutocompleteEditViewGtk::Update(const TabContents* contents) { |
159 // NOTE: We're getting the URL text here from the ToolbarModel. | 200 // NOTE: We're getting the URL text here from the ToolbarModel. |
160 bool visibly_changed_permanent_text = | 201 bool visibly_changed_permanent_text = |
161 model_->UpdatePermanentText(toolbar_model_->GetText()); | 202 model_->UpdatePermanentText(toolbar_model_->GetText()); |
162 | 203 |
163 ToolbarModel::SecurityLevel security_level = | 204 ToolbarModel::SecurityLevel security_level = |
164 toolbar_model_->GetSchemeSecurityLevel(); | 205 toolbar_model_->GetSchemeSecurityLevel(); |
165 bool changed_security_level = (security_level != scheme_security_level_); | 206 bool changed_security_level = (security_level != scheme_security_level_); |
166 scheme_security_level_ = security_level; | 207 scheme_security_level_ = security_level; |
167 | 208 |
168 // TODO(deanm): This doesn't exactly match Windows. There there is a member | 209 // TODO(deanm): This doesn't exactly match Windows. There there is a member |
169 // background_color_. I think we can get away with just the level though. | 210 // background_color_. I think we can get away with just the level though. |
170 if (changed_security_level) { | 211 if (changed_security_level) { |
171 gtk_widget_modify_base(text_view_, GTK_STATE_NORMAL, | 212 gtk_widget_modify_base(text_view_, GTK_STATE_NORMAL, |
172 &LocationBarViewGtk::kBackgroundColorByLevel[security_level]); | 213 &LocationBarViewGtk::kBackgroundColorByLevel[security_level]); |
173 } | 214 } |
174 | 215 |
175 if (contents) { | 216 if (contents) { |
| 217 selected_text_.clear(); |
| 218 selection_saved_ = false; |
176 RevertAll(); | 219 RevertAll(); |
177 // TODO(deanm): Tab switching. The Windows code puts some state in a | 220 const AutocompleteEditState* state = |
178 // PropertyBag on the tab contents, and restores state from there. | 221 GetStateAccessor()->GetProperty(contents->property_bag()); |
| 222 if (state) { |
| 223 model_->RestoreState(state->model_state); |
| 224 |
| 225 // Move the marks for the cursor and the other end of the selection to |
| 226 // the previously-saved offsets. |
| 227 GtkTextIter selection_iter, insert_iter; |
| 228 ItersFromCharRange( |
| 229 state->view_state.selection_range, &selection_iter, &insert_iter); |
| 230 // TODO(derat): Restore the selection range instead of just the cursor |
| 231 // ("insert") position. This in itself is trivial to do using |
| 232 // gtk_text_buffer_select_range(), but then it also becomes necessary to |
| 233 // invalidate hidden tabs' saved ranges when another tab or another app |
| 234 // takes the selection, lest we incorrectly regrab a stale selection when |
| 235 // a hidden tab is later shown. |
| 236 gtk_text_buffer_place_cursor(text_buffer_, &insert_iter); |
| 237 } |
179 } else if (visibly_changed_permanent_text) { | 238 } else if (visibly_changed_permanent_text) { |
180 RevertAll(); | 239 RevertAll(); |
181 // TODO(deanm): There should be code to restore select all here. | 240 // TODO(deanm): There should be code to restore select all here. |
182 } else if (changed_security_level) { | 241 } else if (changed_security_level) { |
183 EmphasizeURLComponents(); | 242 EmphasizeURLComponents(); |
184 } | 243 } |
185 } | 244 } |
186 | 245 |
187 void AutocompleteEditViewGtk::OpenURL(const GURL& url, | 246 void AutocompleteEditViewGtk::OpenURL(const GURL& url, |
188 WindowOpenDisposition disposition, | 247 WindowOpenDisposition disposition, |
(...skipping 99 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
288 // TODO(deanm): Ignoring save_original_selection here, etc. | 347 // TODO(deanm): Ignoring save_original_selection here, etc. |
289 SetWindowTextAndCaretPos(display_text, display_text.length()); | 348 SetWindowTextAndCaretPos(display_text, display_text.length()); |
290 TextChanged(); | 349 TextChanged(); |
291 } | 350 } |
292 | 351 |
293 bool AutocompleteEditViewGtk::OnInlineAutocompleteTextMaybeChanged( | 352 bool AutocompleteEditViewGtk::OnInlineAutocompleteTextMaybeChanged( |
294 const std::wstring& display_text, | 353 const std::wstring& display_text, |
295 size_t user_text_length) { | 354 size_t user_text_length) { |
296 if (display_text == GetText()) | 355 if (display_text == GetText()) |
297 return false; | 356 return false; |
298 | 357 |
299 // We need to get the clipboard while it's attached to the toplevel. The | 358 // We need to get the clipboard while it's attached to the toplevel. The |
300 // easiest thing to do is just to lazily pull the clipboard here. | 359 // easiest thing to do is just to lazily pull the clipboard here. |
301 if (primary_clipboard_ == NULL) { | 360 GtkClipboard* clipboard = |
302 primary_clipboard_ = gtk_widget_get_clipboard(text_view_, | 361 gtk_widget_get_clipboard(text_view_, GDK_SELECTION_PRIMARY); |
303 GDK_SELECTION_PRIMARY); | 362 DCHECK(clipboard); |
304 } | 363 if (!clipboard) |
| 364 return true; |
305 | 365 |
306 // Remove the PRIMARY clipboard to avoid having "clipboard helpers" like | 366 // Remove the PRIMARY clipboard to avoid having "clipboard helpers" like |
307 // klipper and glipper race with / remove our inline autocomplete selection. | 367 // klipper and glipper race with / remove our inline autocomplete selection. |
308 gtk_text_buffer_remove_selection_clipboard(text_buffer_, primary_clipboard_); | 368 gtk_text_buffer_remove_selection_clipboard(text_buffer_, clipboard); |
309 SetWindowTextAndCaretPos(display_text, 0); | 369 SetWindowTextAndCaretPos(display_text, 0); |
310 | 370 |
311 // Select the part of the text that was inline autocompleted. | 371 // Select the part of the text that was inline autocompleted. |
312 GtkTextIter bound, insert; | 372 GtkTextIter bound, insert; |
313 gtk_text_buffer_get_bounds(text_buffer_, &insert, &bound); | 373 gtk_text_buffer_get_bounds(text_buffer_, &insert, &bound); |
314 gtk_text_buffer_get_iter_at_offset(text_buffer_, &insert, user_text_length); | 374 gtk_text_buffer_get_iter_at_offset(text_buffer_, &insert, user_text_length); |
315 gtk_text_buffer_select_range(text_buffer_, &insert, &bound); | 375 gtk_text_buffer_select_range(text_buffer_, &insert, &bound); |
316 | 376 |
317 TextChanged(); | 377 TextChanged(); |
318 // Put the PRIMARY clipboard back, so that selection still somewhat works. | 378 // Put the PRIMARY clipboard back, so that selection still somewhat works. |
319 gtk_text_buffer_add_selection_clipboard(text_buffer_, primary_clipboard_); | 379 gtk_text_buffer_add_selection_clipboard(text_buffer_, clipboard); |
320 | 380 |
321 return true; | 381 return true; |
322 } | 382 } |
323 | 383 |
324 void AutocompleteEditViewGtk::OnRevertTemporaryText() { | 384 void AutocompleteEditViewGtk::OnRevertTemporaryText() { |
325 NOTIMPLEMENTED(); | 385 NOTIMPLEMENTED(); |
326 } | 386 } |
327 | 387 |
328 void AutocompleteEditViewGtk::OnBeforePossibleChange() { | 388 void AutocompleteEditViewGtk::OnBeforePossibleChange() { |
329 // Record our state. | 389 // Record our state. |
(...skipping 112 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
442 // clicked handling code. If we were to try to set the selection from | 502 // clicked handling code. If we were to try to set the selection from |
443 // the focus-in event, it's just going to be undone by the click handler. | 503 // the focus-in event, it's just going to be undone by the click handler. |
444 // This is a bit ugly. We shim in to get the click before the GtkTextView, | 504 // This is a bit ugly. We shim in to get the click before the GtkTextView, |
445 // then if we don't have focus, we (hopefully safely) assume that the click | 505 // then if we don't have focus, we (hopefully safely) assume that the click |
446 // will cause us to become focused. We call GtkTextView's default handler | 506 // will cause us to become focused. We call GtkTextView's default handler |
447 // and then stop propagation. This allows us to run our code after the | 507 // and then stop propagation. This allows us to run our code after the |
448 // default handler, even if that handler stopped propagation. | 508 // default handler, even if that handler stopped propagation. |
449 if (GTK_WIDGET_HAS_FOCUS(text_view_)) | 509 if (GTK_WIDGET_HAS_FOCUS(text_view_)) |
450 return FALSE; // Continue to propagate into the GtkTextView handler. | 510 return FALSE; // Continue to propagate into the GtkTextView handler. |
451 | 511 |
| 512 // We only want to select everything on left-click; otherwise we'll end up |
| 513 // stealing the PRIMARY selection when the user middle-clicks to paste it |
| 514 // here. |
| 515 if (event->button != 1) |
| 516 return FALSE; |
| 517 |
452 // Call the GtkTextView default handler, ignoring the fact that it will | 518 // Call the GtkTextView default handler, ignoring the fact that it will |
453 // likely have told us to stop propagating. We want to handle selection. | 519 // likely have told us to stop propagating. We want to handle selection. |
454 GtkWidgetClass* klass = GTK_WIDGET_GET_CLASS(text_view_); | 520 GtkWidgetClass* klass = GTK_WIDGET_GET_CLASS(text_view_); |
455 klass->button_press_event(text_view_, event); | 521 klass->button_press_event(text_view_, event); |
456 | 522 |
457 // Select the full input when we get focus. | 523 // Select the full input when we get focus. |
458 SelectAll(false); | 524 SelectAll(false); |
459 // So we told the buffer where the cursor should be, but make sure to tell | 525 // So we told the buffer where the cursor should be, but make sure to tell |
460 // the view so it can scroll it to be visible if needed. | 526 // the view so it can scroll it to be visible if needed. |
461 // NOTE: This function doesn't seem to like a count of 0, looking at the | 527 // NOTE: This function doesn't seem to like a count of 0, looking at the |
(...skipping 76 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
538 gtk_clipboard_request_text(x_clipboard, HandlePasteAndGoReceivedTextThunk, | 604 gtk_clipboard_request_text(x_clipboard, HandlePasteAndGoReceivedTextThunk, |
539 this); | 605 this); |
540 } | 606 } |
541 | 607 |
542 void AutocompleteEditViewGtk::HandlePasteAndGoReceivedText( | 608 void AutocompleteEditViewGtk::HandlePasteAndGoReceivedText( |
543 const std::wstring& text) { | 609 const std::wstring& text) { |
544 if (model_->CanPasteAndGo(text)) | 610 if (model_->CanPasteAndGo(text)) |
545 model_->PasteAndGo(); | 611 model_->PasteAndGo(); |
546 } | 612 } |
547 | 613 |
| 614 void AutocompleteEditViewGtk::HandleMarkSet(GtkTextBuffer* buffer, |
| 615 GtkTextIter* location, |
| 616 GtkTextMark* mark) { |
| 617 if (!text_buffer_ || buffer != text_buffer_) |
| 618 return; |
| 619 |
| 620 if (mark != gtk_text_buffer_get_insert(text_buffer_) && |
| 621 mark != gtk_text_buffer_get_selection_bound(text_buffer_)) { |
| 622 return; |
| 623 } |
| 624 |
| 625 // Is no text selected in the GtkTextView? |
| 626 bool no_text_selected = false; |
| 627 |
| 628 // Get the currently-selected text, if there is any. |
| 629 GtkTextIter start, end; |
| 630 if (!gtk_text_buffer_get_selection_bounds(text_buffer_, &start, &end)) { |
| 631 no_text_selected = true; |
| 632 } else { |
| 633 gchar* text = gtk_text_iter_get_text(&start, &end); |
| 634 size_t text_len = strlen(text); |
| 635 if (!text_len) { |
| 636 no_text_selected = true; |
| 637 } else { |
| 638 selected_text_ = std::string(text, text_len); |
| 639 selection_saved_ = false; |
| 640 } |
| 641 g_free(text); |
| 642 } |
| 643 |
| 644 // If we have some previously-selected text but it's no longer highlighted |
| 645 // and we haven't saved it as the selection yet, we save it now. |
| 646 if (!selected_text_.empty() && no_text_selected && !selection_saved_) { |
| 647 SavePrimarySelection(selected_text_); |
| 648 selection_saved_ = true; |
| 649 } |
| 650 } |
| 651 |
548 AutocompleteEditViewGtk::CharRange AutocompleteEditViewGtk::GetSelection() { | 652 AutocompleteEditViewGtk::CharRange AutocompleteEditViewGtk::GetSelection() { |
549 // You can not just use get_selection_bounds here, since the order will be | 653 // You can not just use get_selection_bounds here, since the order will be |
550 // ascending, and you don't know where the user's start and end of the | 654 // ascending, and you don't know where the user's start and end of the |
551 // selection was (if the selection was forwards or backwards). Get the | 655 // selection was (if the selection was forwards or backwards). Get the |
552 // actual marks so that we can preserve the selection direction. | 656 // actual marks so that we can preserve the selection direction. |
553 GtkTextIter start, insert; | 657 GtkTextIter start, insert; |
554 GtkTextMark* mark; | 658 GtkTextMark* mark; |
555 | 659 |
556 mark = gtk_text_buffer_get_selection_bound(text_buffer_); | 660 mark = gtk_text_buffer_get_selection_bound(text_buffer_); |
557 gtk_text_buffer_get_iter_at_mark(text_buffer_, &start, mark); | 661 gtk_text_buffer_get_iter_at_mark(text_buffer_, &start, mark); |
(...skipping 70 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
628 gtk_text_buffer_apply_tag(text_buffer_, insecure_scheme_tag_, | 732 gtk_text_buffer_apply_tag(text_buffer_, insecure_scheme_tag_, |
629 &start, &end); | 733 &start, &end); |
630 } | 734 } |
631 } | 735 } |
632 } | 736 } |
633 | 737 |
634 void AutocompleteEditViewGtk::TextChanged() { | 738 void AutocompleteEditViewGtk::TextChanged() { |
635 EmphasizeURLComponents(); | 739 EmphasizeURLComponents(); |
636 controller_->OnChanged(); | 740 controller_->OnChanged(); |
637 } | 741 } |
| 742 |
| 743 void AutocompleteEditViewGtk::SavePrimarySelection( |
| 744 const std::string& selected_text) { |
| 745 GtkClipboard* clipboard = |
| 746 gtk_widget_get_clipboard(text_view_, GDK_SELECTION_PRIMARY); |
| 747 DCHECK(clipboard); |
| 748 if (!clipboard) |
| 749 return; |
| 750 |
| 751 gtk_clipboard_set_text( |
| 752 clipboard, selected_text.data(), selected_text.size()); |
| 753 } |
OLD | NEW |