| 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 |