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 <algorithm> | 10 #include <algorithm> |
(...skipping 86 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
97 popup_window_mode_(popup_window_mode), | 97 popup_window_mode_(popup_window_mode), |
98 scheme_security_level_(ToolbarModel::NORMAL), | 98 scheme_security_level_(ToolbarModel::NORMAL), |
99 selection_saved_(false), | 99 selection_saved_(false), |
100 mark_set_handler_id_(0), | 100 mark_set_handler_id_(0), |
101 button_1_pressed_(false), | 101 button_1_pressed_(false), |
102 text_selected_during_click_(false), | 102 text_selected_during_click_(false), |
103 text_view_focused_before_button_press_(false), | 103 text_view_focused_before_button_press_(false), |
104 #if !defined(TOOLKIT_VIEWS) | 104 #if !defined(TOOLKIT_VIEWS) |
105 theme_provider_(GtkThemeProvider::GetFrom(profile)), | 105 theme_provider_(GtkThemeProvider::GetFrom(profile)), |
106 #endif | 106 #endif |
107 tab_was_pressed_(false) { | 107 tab_was_pressed_(false), |
| 108 paste_clipboard_requested_(false) { |
108 model_->set_popup_model(popup_view_->GetModel()); | 109 model_->set_popup_model(popup_view_->GetModel()); |
109 } | 110 } |
110 | 111 |
111 AutocompleteEditViewGtk::~AutocompleteEditViewGtk() { | 112 AutocompleteEditViewGtk::~AutocompleteEditViewGtk() { |
112 NotificationService::current()->Notify( | 113 NotificationService::current()->Notify( |
113 NotificationType::AUTOCOMPLETE_EDIT_DESTROYED, | 114 NotificationType::AUTOCOMPLETE_EDIT_DESTROYED, |
114 Source<AutocompleteEditViewGtk>(this), | 115 Source<AutocompleteEditViewGtk>(this), |
115 NotificationService::NoDetails()); | 116 NotificationService::NoDetails()); |
116 | 117 |
117 // Explicitly teardown members which have a reference to us. Just to be safe | 118 // Explicitly teardown members which have a reference to us. Just to be safe |
(...skipping 86 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
204 g_signal_connect(text_view_, "populate-popup", | 205 g_signal_connect(text_view_, "populate-popup", |
205 G_CALLBACK(&HandlePopulatePopupThunk), this); | 206 G_CALLBACK(&HandlePopulatePopupThunk), this); |
206 mark_set_handler_id_ = g_signal_connect( | 207 mark_set_handler_id_ = g_signal_connect( |
207 text_buffer_, "mark-set", G_CALLBACK(&HandleMarkSetThunk), this); | 208 text_buffer_, "mark-set", G_CALLBACK(&HandleMarkSetThunk), this); |
208 g_signal_connect(text_view_, "drag-data-received", | 209 g_signal_connect(text_view_, "drag-data-received", |
209 G_CALLBACK(&HandleDragDataReceivedThunk), this); | 210 G_CALLBACK(&HandleDragDataReceivedThunk), this); |
210 g_signal_connect(text_view_, "backspace", | 211 g_signal_connect(text_view_, "backspace", |
211 G_CALLBACK(&HandleBackSpaceThunk), this); | 212 G_CALLBACK(&HandleBackSpaceThunk), this); |
212 g_signal_connect(text_view_, "copy-clipboard", | 213 g_signal_connect(text_view_, "copy-clipboard", |
213 G_CALLBACK(&HandleCopyClipboardThunk), this); | 214 G_CALLBACK(&HandleCopyClipboardThunk), this); |
| 215 g_signal_connect(text_view_, "paste-clipboard", |
| 216 G_CALLBACK(&HandlePasteClipboardThunk), this); |
214 | 217 |
215 #if !defined(TOOLKIT_VIEWS) | 218 #if !defined(TOOLKIT_VIEWS) |
216 registrar_.Add(this, | 219 registrar_.Add(this, |
217 NotificationType::BROWSER_THEME_CHANGED, | 220 NotificationType::BROWSER_THEME_CHANGED, |
218 NotificationService::AllSources()); | 221 NotificationService::AllSources()); |
219 theme_provider_->InitThemesFor(this); | 222 theme_provider_->InitThemesFor(this); |
220 #else | 223 #else |
221 // Manually invoke SetBaseColor() because TOOLKIT_VIEWS doesn't observe | 224 // Manually invoke SetBaseColor() because TOOLKIT_VIEWS doesn't observe |
222 // themes. | 225 // themes. |
223 SetBaseColor(); | 226 SetBaseColor(); |
(...skipping 129 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
353 gtk_text_buffer_get_bounds(text_buffer_, &start, &end); | 356 gtk_text_buffer_get_bounds(text_buffer_, &start, &end); |
354 gtk_text_buffer_get_iter_at_offset(text_buffer_, &start, 1); | 357 gtk_text_buffer_get_iter_at_offset(text_buffer_, &start, 1); |
355 StartUpdatingHighlightedText(); | 358 StartUpdatingHighlightedText(); |
356 gtk_text_buffer_select_range(text_buffer_, &start, &end); | 359 gtk_text_buffer_select_range(text_buffer_, &start, &end); |
357 FinishUpdatingHighlightedText(); | 360 FinishUpdatingHighlightedText(); |
358 } | 361 } |
359 } | 362 } |
360 | 363 |
361 bool AutocompleteEditViewGtk::IsSelectAll() { | 364 bool AutocompleteEditViewGtk::IsSelectAll() { |
362 GtkTextIter sel_start, sel_end; | 365 GtkTextIter sel_start, sel_end; |
363 if (!gtk_text_buffer_get_selection_bounds(text_buffer_, &sel_start, &sel_end)) | 366 gtk_text_buffer_get_selection_bounds(text_buffer_, &sel_start, &sel_end); |
364 return false; | |
365 | 367 |
366 GtkTextIter start, end; | 368 GtkTextIter start, end; |
367 gtk_text_buffer_get_bounds(text_buffer_, &start, &end); | 369 gtk_text_buffer_get_bounds(text_buffer_, &start, &end); |
368 | 370 |
| 371 // Returns true if the |text_buffer_| is empty. |
369 return gtk_text_iter_equal(&start, &sel_start) && | 372 return gtk_text_iter_equal(&start, &sel_start) && |
370 gtk_text_iter_equal(&end, &sel_end); | 373 gtk_text_iter_equal(&end, &sel_end); |
371 } | 374 } |
372 | 375 |
373 void AutocompleteEditViewGtk::SelectAll(bool reversed) { | 376 void AutocompleteEditViewGtk::SelectAll(bool reversed) { |
374 // SelectAll() is invoked as a side effect of other actions (e.g. switching | 377 // SelectAll() is invoked as a side effect of other actions (e.g. switching |
375 // tabs or hitting Escape) in autocomplete_edit.cc, so we don't update the | 378 // tabs or hitting Escape) in autocomplete_edit.cc, so we don't update the |
376 // PRIMARY selection here. | 379 // PRIMARY selection here. |
377 // TODO(derat): But this is also called by LocationBarView::FocusLocation() -- | 380 // TODO(derat): But this is also called by LocationBarView::FocusLocation() -- |
378 // should the X selection be updated when the user hits Ctrl-L? | 381 // should the X selection be updated when the user hits Ctrl-L? |
379 SelectAllInternal(reversed, false); | 382 SelectAllInternal(reversed, false); |
380 } | 383 } |
381 | 384 |
382 void AutocompleteEditViewGtk::RevertAll() { | 385 void AutocompleteEditViewGtk::RevertAll() { |
383 ClosePopup(); | 386 ClosePopup(); |
384 model_->Revert(); | 387 model_->Revert(); |
385 TextChanged(); | 388 TextChanged(); |
386 } | 389 } |
387 | 390 |
388 void AutocompleteEditViewGtk::UpdatePopup() { | 391 void AutocompleteEditViewGtk::UpdatePopup() { |
389 model_->SetInputInProgress(true); | 392 model_->SetInputInProgress(true); |
390 if (!model_->has_focus()) | 393 if (!model_->has_focus()) |
391 return; | 394 return; |
392 | 395 |
393 // Don't inline autocomplete when the caret/selection isn't at the end of | 396 // Don't inline autocomplete when the caret/selection isn't at the end of |
394 // the text. | 397 // the text. |
395 CharRange sel = GetSelection(); | 398 CharRange sel = GetSelection(); |
396 model_->StartAutocomplete(sel.cp_max < GetTextLength()); | 399 model_->StartAutocomplete(std::max(sel.cp_max, sel.cp_min) < GetTextLength()); |
397 } | 400 } |
398 | 401 |
399 void AutocompleteEditViewGtk::ClosePopup() { | 402 void AutocompleteEditViewGtk::ClosePopup() { |
400 popup_view_->GetModel()->StopAutocomplete(); | 403 popup_view_->GetModel()->StopAutocomplete(); |
401 } | 404 } |
402 | 405 |
403 void AutocompleteEditViewGtk::OnTemporaryTextMaybeChanged( | 406 void AutocompleteEditViewGtk::OnTemporaryTextMaybeChanged( |
404 const std::wstring& display_text, | 407 const std::wstring& display_text, |
405 bool save_original_selection) { | 408 bool save_original_selection) { |
406 if (save_original_selection) { | 409 if (save_original_selection) { |
(...skipping 29 matching lines...) Expand all Loading... |
436 | 439 |
437 void AutocompleteEditViewGtk::OnRevertTemporaryText() { | 440 void AutocompleteEditViewGtk::OnRevertTemporaryText() { |
438 StartUpdatingHighlightedText(); | 441 StartUpdatingHighlightedText(); |
439 SetTextAndSelectedRange(saved_temporary_text_, saved_temporary_selection_); | 442 SetTextAndSelectedRange(saved_temporary_text_, saved_temporary_selection_); |
440 FinishUpdatingHighlightedText(); | 443 FinishUpdatingHighlightedText(); |
441 saved_temporary_text_.clear(); | 444 saved_temporary_text_.clear(); |
442 TextChanged(); | 445 TextChanged(); |
443 } | 446 } |
444 | 447 |
445 void AutocompleteEditViewGtk::OnBeforePossibleChange() { | 448 void AutocompleteEditViewGtk::OnBeforePossibleChange() { |
| 449 // If this change is caused by a paste clipboard action and all text is |
| 450 // selected, then call model_->on_paste_replacing_all() to prevent inline |
| 451 // autocomplete. |
| 452 if (paste_clipboard_requested_) { |
| 453 paste_clipboard_requested_ = false; |
| 454 if (IsSelectAll()) |
| 455 model_->on_paste_replacing_all(); |
| 456 } |
| 457 |
446 // Record our state. | 458 // Record our state. |
447 text_before_change_ = GetText(); | 459 text_before_change_ = GetText(); |
448 sel_before_change_ = GetSelection(); | 460 sel_before_change_ = GetSelection(); |
449 } | 461 } |
450 | 462 |
451 // TODO(deanm): This is mostly stolen from Windows, and will need some work. | 463 // TODO(deanm): This is mostly stolen from Windows, and will need some work. |
452 bool AutocompleteEditViewGtk::OnAfterPossibleChange() { | 464 bool AutocompleteEditViewGtk::OnAfterPossibleChange() { |
453 CharRange new_sel = GetSelection(); | 465 CharRange new_sel = GetSelection(); |
454 int length = GetTextLength(); | 466 int length = GetTextLength(); |
455 bool selection_differs = (new_sel.cp_min != sel_before_change_.cp_min) || | 467 bool selection_differs = (new_sel.cp_min != sel_before_change_.cp_min) || |
(...skipping 149 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
605 } | 617 } |
606 | 618 |
607 // Set |tab_was_pressed_| to true if it's a Tab key press event, so that our | 619 // Set |tab_was_pressed_| to true if it's a Tab key press event, so that our |
608 // handler of "move-focus" signal can trigger Tab to search behavior when | 620 // handler of "move-focus" signal can trigger Tab to search behavior when |
609 // necessary. | 621 // necessary. |
610 tab_was_pressed_ = ((event->keyval == GDK_Tab || | 622 tab_was_pressed_ = ((event->keyval == GDK_Tab || |
611 event->keyval == GDK_ISO_Left_Tab || | 623 event->keyval == GDK_ISO_Left_Tab || |
612 event->keyval == GDK_KP_Tab) && | 624 event->keyval == GDK_KP_Tab) && |
613 !(event->state & GDK_CONTROL_MASK)); | 625 !(event->state & GDK_CONTROL_MASK)); |
614 | 626 |
| 627 // Reset |paste_clipboard_requested_| to make sure we won't misinterpret this |
| 628 // key input action as a paste action. |
| 629 paste_clipboard_requested_ = false; |
| 630 |
615 // Call the default handler, so that IME can work as normal. | 631 // Call the default handler, so that IME can work as normal. |
616 // New line characters will be filtered out by our "insert-text" | 632 // New line characters will be filtered out by our "insert-text" |
617 // signal handler attached to |text_buffer_| object. | 633 // signal handler attached to |text_buffer_| object. |
618 gboolean result = klass->key_press_event(widget, event); | 634 gboolean result = klass->key_press_event(widget, event); |
619 | 635 |
620 // Set |tab_was_pressed_| to false, to make sure Tab to search behavior can | 636 // Set |tab_was_pressed_| to false, to make sure Tab to search behavior can |
621 // only be triggered by pressing Tab key. | 637 // only be triggered by pressing Tab key. |
622 tab_was_pressed_ = false; | 638 tab_was_pressed_ = false; |
623 | 639 |
624 if (enter_pressed && (char_inserted_ == '\n' || char_inserted_ == '\r')) { | 640 if (enter_pressed && (char_inserted_ == '\n' || char_inserted_ == '\r')) { |
(...skipping 52 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
677 model_->OnControlKeyChanged(false); | 693 model_->OnControlKeyChanged(false); |
678 } | 694 } |
679 | 695 |
680 // Even though we handled the press ourselves, let GtkTextView handle the | 696 // Even though we handled the press ourselves, let GtkTextView handle the |
681 // release. It shouldn't do anything particularly interesting, but it will | 697 // release. It shouldn't do anything particularly interesting, but it will |
682 // handle the IME work for us. | 698 // handle the IME work for us. |
683 return FALSE; // Propagate into GtkTextView. | 699 return FALSE; // Propagate into GtkTextView. |
684 } | 700 } |
685 | 701 |
686 gboolean AutocompleteEditViewGtk::HandleViewButtonPress(GdkEventButton* event) { | 702 gboolean AutocompleteEditViewGtk::HandleViewButtonPress(GdkEventButton* event) { |
| 703 // We don't need to care about double and triple clicks. |
| 704 if (event->type != GDK_BUTTON_PRESS) |
| 705 return FALSE; |
| 706 |
687 if (event->button == 1) { | 707 if (event->button == 1) { |
688 // When the first button is pressed, track some stuff that will help us | 708 // When the first button is pressed, track some stuff that will help us |
689 // determine whether we should select all of the text when the button is | 709 // determine whether we should select all of the text when the button is |
690 // released. | 710 // released. |
691 button_1_pressed_ = true; | 711 button_1_pressed_ = true; |
692 text_view_focused_before_button_press_ = GTK_WIDGET_HAS_FOCUS(text_view_); | 712 text_view_focused_before_button_press_ = GTK_WIDGET_HAS_FOCUS(text_view_); |
693 text_selected_during_click_ = false; | 713 text_selected_during_click_ = false; |
| 714 } else if (event->button == 2) { |
| 715 // GtkTextView pastes PRIMARY selection with middle click. |
| 716 // We can't call model_->on_paste_replacing_all() here, because the actual |
| 717 // paste clipboard action may not be performed if the clipboard is empty. |
| 718 paste_clipboard_requested_ = true; |
694 } | 719 } |
695 return FALSE; | 720 return FALSE; |
696 } | 721 } |
697 | 722 |
698 gboolean AutocompleteEditViewGtk::HandleViewButtonRelease( | 723 gboolean AutocompleteEditViewGtk::HandleViewButtonRelease( |
699 GdkEventButton* event) { | 724 GdkEventButton* event) { |
700 if (event->button != 1) | 725 if (event->button != 1) |
701 return FALSE; | 726 return FALSE; |
702 | 727 |
703 button_1_pressed_ = false; | 728 button_1_pressed_ = false; |
(...skipping 14 matching lines...) Expand all Loading... |
718 // code it will skip an important loop. Use -1 to achieve the same. | 743 // code it will skip an important loop. Use -1 to achieve the same. |
719 GtkTextIter start, end; | 744 GtkTextIter start, end; |
720 gtk_text_buffer_get_bounds(text_buffer_, &start, &end); | 745 gtk_text_buffer_get_bounds(text_buffer_, &start, &end); |
721 gtk_text_view_move_visually(GTK_TEXT_VIEW(text_view_), &start, -1); | 746 gtk_text_view_move_visually(GTK_TEXT_VIEW(text_view_), &start, -1); |
722 } | 747 } |
723 | 748 |
724 return TRUE; // Don't continue, we called the default handler already. | 749 return TRUE; // Don't continue, we called the default handler already. |
725 } | 750 } |
726 | 751 |
727 gboolean AutocompleteEditViewGtk::HandleViewFocusIn() { | 752 gboolean AutocompleteEditViewGtk::HandleViewFocusIn() { |
728 model_->OnSetFocus(false); | 753 GdkModifierType modifiers; |
| 754 gdk_window_get_pointer(text_view_->window, NULL, NULL, &modifiers); |
| 755 model_->OnSetFocus((modifiers & GDK_CONTROL_MASK) != 0); |
729 // TODO(deanm): Some keyword hit business, etc here. | 756 // TODO(deanm): Some keyword hit business, etc here. |
730 | 757 |
731 return FALSE; // Continue propagation. | 758 return FALSE; // Continue propagation. |
732 } | 759 } |
733 | 760 |
734 gboolean AutocompleteEditViewGtk::HandleViewFocusOut() { | 761 gboolean AutocompleteEditViewGtk::HandleViewFocusOut() { |
735 // Close the popup. | 762 // Close the popup. |
736 ClosePopup(); | 763 ClosePopup(); |
737 // Tell the model to reset itself. | 764 // Tell the model to reset itself. |
738 model_->OnKillFocus(); | 765 model_->OnKillFocus(); |
(...skipping 122 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
861 SavePrimarySelection(selected_text_); | 888 SavePrimarySelection(selected_text_); |
862 selection_saved_ = true; | 889 selection_saved_ = true; |
863 } | 890 } |
864 } | 891 } |
865 | 892 |
866 // Just use the default behavior for DnD, except if the drop can be a PasteAndGo | 893 // Just use the default behavior for DnD, except if the drop can be a PasteAndGo |
867 // then override. | 894 // then override. |
868 void AutocompleteEditViewGtk::HandleDragDataReceived( | 895 void AutocompleteEditViewGtk::HandleDragDataReceived( |
869 GdkDragContext* context, gint x, gint y, | 896 GdkDragContext* context, gint x, gint y, |
870 GtkSelectionData* selection_data, guint target_type, guint time) { | 897 GtkSelectionData* selection_data, guint target_type, guint time) { |
| 898 // Reset |paste_clipboard_requested_| to make sure we won't misinterpret this |
| 899 // drop action as a paste action. |
| 900 paste_clipboard_requested_ = false; |
| 901 |
871 // Don't try to PasteAndGo on drops originating from this omnibox. However, do | 902 // Don't try to PasteAndGo on drops originating from this omnibox. However, do |
872 // allow default behavior for such drags. | 903 // allow default behavior for such drags. |
873 if (context->source_window == text_view_->window) | 904 if (context->source_window == text_view_->window) |
874 return; | 905 return; |
875 | 906 |
876 guchar* text = gtk_selection_data_get_text(selection_data); | 907 guchar* text = gtk_selection_data_get_text(selection_data); |
877 if (!text) | 908 if (!text) |
878 return; | 909 return; |
879 | 910 |
880 std::wstring possible_url = UTF8ToWide(reinterpret_cast<char*>(text)); | 911 std::wstring possible_url = UTF8ToWide(reinterpret_cast<char*>(text)); |
(...skipping 103 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
984 // don't want to do (and it also appears to at least sometimes trigger a | 1015 // don't want to do (and it also appears to at least sometimes trigger a |
985 // failed G_IS_OBJECT() assertion). | 1016 // failed G_IS_OBJECT() assertion). |
986 if (gtk_clipboard_get_owner(clipboard) == G_OBJECT(text_buffer_)) | 1017 if (gtk_clipboard_get_owner(clipboard) == G_OBJECT(text_buffer_)) |
987 return; | 1018 return; |
988 | 1019 |
989 // We can't just call SavePrimarySelection(); that makes the text view lose | 1020 // We can't just call SavePrimarySelection(); that makes the text view lose |
990 // the selection and unhighlight its text. | 1021 // the selection and unhighlight its text. |
991 gtk_text_buffer_copy_clipboard(text_buffer_, clipboard); | 1022 gtk_text_buffer_copy_clipboard(text_buffer_, clipboard); |
992 } | 1023 } |
993 | 1024 |
| 1025 void AutocompleteEditViewGtk::HandlePasteClipboard() { |
| 1026 // We can't call model_->on_paste_replacing_all() here, because the actual |
| 1027 // paste clipboard action may not be performed if the clipboard is empty. |
| 1028 paste_clipboard_requested_ = true; |
| 1029 } |
| 1030 |
994 void AutocompleteEditViewGtk::SelectAllInternal(bool reversed, | 1031 void AutocompleteEditViewGtk::SelectAllInternal(bool reversed, |
995 bool update_primary_selection) { | 1032 bool update_primary_selection) { |
996 GtkTextIter start, end; | 1033 GtkTextIter start, end; |
997 if (reversed) { | 1034 if (reversed) { |
998 gtk_text_buffer_get_bounds(text_buffer_, &end, &start); | 1035 gtk_text_buffer_get_bounds(text_buffer_, &end, &start); |
999 } else { | 1036 } else { |
1000 gtk_text_buffer_get_bounds(text_buffer_, &start, &end); | 1037 gtk_text_buffer_get_bounds(text_buffer_, &start, &end); |
1001 } | 1038 } |
1002 if (!update_primary_selection) | 1039 if (!update_primary_selection) |
1003 StartUpdatingHighlightedText(); | 1040 StartUpdatingHighlightedText(); |
(...skipping 115 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
1119 const std::string& selected_text) { | 1156 const std::string& selected_text) { |
1120 GtkClipboard* clipboard = | 1157 GtkClipboard* clipboard = |
1121 gtk_widget_get_clipboard(text_view_, GDK_SELECTION_PRIMARY); | 1158 gtk_widget_get_clipboard(text_view_, GDK_SELECTION_PRIMARY); |
1122 DCHECK(clipboard); | 1159 DCHECK(clipboard); |
1123 if (!clipboard) | 1160 if (!clipboard) |
1124 return; | 1161 return; |
1125 | 1162 |
1126 gtk_clipboard_set_text( | 1163 gtk_clipboard_set_text( |
1127 clipboard, selected_text.data(), selected_text.size()); | 1164 clipboard, selected_text.data(), selected_text.size()); |
1128 } | 1165 } |
OLD | NEW |