Home | History | Annotate | Download | only in web_contents
      1 // Copyright (c) 2013 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 "content/browser/web_contents/touch_editable_impl_aura.h"
      6 
      7 #include "content/browser/renderer_host/render_widget_host_impl.h"
      8 #include "content/browser/renderer_host/render_widget_host_view_aura.h"
      9 #include "content/browser/web_contents/web_contents_impl.h"
     10 #include "content/common/view_messages.h"
     11 #include "content/public/browser/render_view_host.h"
     12 #include "content/public/browser/render_widget_host.h"
     13 #include "grit/ui_strings.h"
     14 #include "ui/aura/client/screen_position_client.h"
     15 #include "ui/aura/window.h"
     16 #include "ui/aura/window_tree_host.h"
     17 #include "ui/base/clipboard/clipboard.h"
     18 #include "ui/base/ui_base_switches_util.h"
     19 #include "ui/gfx/range/range.h"
     20 #include "ui/wm/public/activation_client.h"
     21 
     22 namespace content {
     23 
     24 ////////////////////////////////////////////////////////////////////////////////
     25 // TouchEditableImplAura, public:
     26 
     27 TouchEditableImplAura::~TouchEditableImplAura() {
     28   Cleanup();
     29 }
     30 
     31 // static
     32 TouchEditableImplAura* TouchEditableImplAura::Create() {
     33   if (switches::IsTouchEditingEnabled())
     34     return new TouchEditableImplAura();
     35   return NULL;
     36 }
     37 
     38 void TouchEditableImplAura::AttachToView(RenderWidgetHostViewAura* view) {
     39   if (rwhva_ == view)
     40     return;
     41 
     42   Cleanup();
     43   if (!view)
     44     return;
     45 
     46   rwhva_ = view;
     47   rwhva_->set_touch_editing_client(this);
     48 }
     49 
     50 void TouchEditableImplAura::UpdateEditingController() {
     51   if (!rwhva_ || !rwhva_->HasFocus())
     52     return;
     53 
     54   if (text_input_type_ != ui::TEXT_INPUT_TYPE_NONE ||
     55       selection_anchor_rect_ != selection_focus_rect_) {
     56     if (touch_selection_controller_)
     57       touch_selection_controller_->SelectionChanged();
     58   } else {
     59     EndTouchEditing(false);
     60   }
     61 }
     62 
     63 void TouchEditableImplAura::OverscrollStarted() {
     64   overscroll_in_progress_ = true;
     65 }
     66 
     67 void TouchEditableImplAura::OverscrollCompleted() {
     68   // We might receive multiple OverscrollStarted() and OverscrollCompleted()
     69   // during the same scroll session (for example, when the scroll direction
     70   // changes). We want to show the handles only when:
     71   // 1. Overscroll has completed
     72   // 2. Scrolling session is over, i.e. we have received ET_GESTURE_SCROLL_END.
     73   // 3. If we had hidden the handles when scrolling started
     74   // 4. If there is still a need to show handles (there is a non-empty selection
     75   // or non-NONE |text_input_type_|)
     76   if (overscroll_in_progress_ && !scroll_in_progress_ &&
     77       handles_hidden_due_to_scroll_ &&
     78       (selection_anchor_rect_ != selection_focus_rect_ ||
     79           text_input_type_ != ui::TEXT_INPUT_TYPE_NONE)) {
     80     StartTouchEditing();
     81     UpdateEditingController();
     82   }
     83   overscroll_in_progress_ = false;
     84 }
     85 
     86 ////////////////////////////////////////////////////////////////////////////////
     87 // TouchEditableImplAura, RenderWidgetHostViewAura::TouchEditingClient
     88 // implementation:
     89 
     90 void TouchEditableImplAura::StartTouchEditing() {
     91   if (!rwhva_ || !rwhva_->HasFocus())
     92     return;
     93 
     94   if (!touch_selection_controller_) {
     95     touch_selection_controller_.reset(
     96         ui::TouchSelectionController::create(this));
     97   }
     98   if (touch_selection_controller_)
     99     touch_selection_controller_->SelectionChanged();
    100 }
    101 
    102 void TouchEditableImplAura::EndTouchEditing(bool quick) {
    103   if (touch_selection_controller_) {
    104     if (touch_selection_controller_->IsHandleDragInProgress()) {
    105       touch_selection_controller_->SelectionChanged();
    106     } else {
    107       selection_gesture_in_process_ = false;
    108       touch_selection_controller_->HideHandles(quick);
    109       touch_selection_controller_.reset();
    110     }
    111   }
    112 }
    113 
    114 void TouchEditableImplAura::OnSelectionOrCursorChanged(const gfx::Rect& anchor,
    115                                                        const gfx::Rect& focus) {
    116   selection_anchor_rect_ = anchor;
    117   selection_focus_rect_ = focus;
    118 
    119   // If touch editing handles were not visible, we bring them up only if
    120   // there is non-zero selection on the page. And the current event is a
    121   // gesture event (we dont want to show handles if the user is selecting
    122   // using mouse or keyboard).
    123   if (selection_gesture_in_process_ && !scroll_in_progress_ &&
    124       !overscroll_in_progress_ &&
    125       selection_anchor_rect_ != selection_focus_rect_) {
    126     StartTouchEditing();
    127     selection_gesture_in_process_ = false;
    128   }
    129 
    130   UpdateEditingController();
    131 }
    132 
    133 void TouchEditableImplAura::OnTextInputTypeChanged(ui::TextInputType type) {
    134   text_input_type_ = type;
    135 }
    136 
    137 bool TouchEditableImplAura::HandleInputEvent(const ui::Event* event) {
    138   DCHECK(rwhva_);
    139   if (!event->IsGestureEvent()) {
    140     // Ignore all non-gesture events. Non-gesture events that can deactivate
    141     // touch editing are handled in TouchSelectionControllerImpl.
    142     return false;
    143   }
    144 
    145   const ui::GestureEvent* gesture_event =
    146       static_cast<const ui::GestureEvent*>(event);
    147   switch (event->type()) {
    148     case ui::ET_GESTURE_TAP:
    149       // When the user taps, we want to show touch editing handles if user
    150       // tapped on selected text.
    151       if (gesture_event->details().tap_count() == 1 &&
    152           selection_anchor_rect_ != selection_focus_rect_) {
    153         // UnionRects only works for rects with non-zero width.
    154         gfx::Rect anchor(selection_anchor_rect_.origin(),
    155                          gfx::Size(1, selection_anchor_rect_.height()));
    156         gfx::Rect focus(selection_focus_rect_.origin(),
    157                         gfx::Size(1, selection_focus_rect_.height()));
    158         gfx::Rect selection_rect = gfx::UnionRects(anchor, focus);
    159         if (selection_rect.Contains(gesture_event->location())) {
    160           StartTouchEditing();
    161           return true;
    162         }
    163       }
    164       // For single taps, not inside selected region, we want to show handles
    165       // only when the tap is on an already focused textfield.
    166       textfield_was_focused_on_tap_ = false;
    167       if (gesture_event->details().tap_count() == 1 &&
    168           text_input_type_ != ui::TEXT_INPUT_TYPE_NONE)
    169         textfield_was_focused_on_tap_ = true;
    170       break;
    171     case ui::ET_GESTURE_LONG_PRESS:
    172       selection_gesture_in_process_ = true;
    173       break;
    174     case ui::ET_GESTURE_SCROLL_BEGIN:
    175       // If selection handles are currently visible, we want to get them back up
    176       // when scrolling ends. So we set |handles_hidden_due_to_scroll_| so that
    177       // we can re-start touch editing on scroll end gesture.
    178       scroll_in_progress_ = true;
    179       handles_hidden_due_to_scroll_ = false;
    180       if (touch_selection_controller_)
    181         handles_hidden_due_to_scroll_ = true;
    182       EndTouchEditing(true);
    183       break;
    184     case ui::ET_GESTURE_SCROLL_END:
    185       // Scroll has ended, but we might still be in overscroll animation.
    186       if (handles_hidden_due_to_scroll_ && !overscroll_in_progress_ &&
    187           (selection_anchor_rect_ != selection_focus_rect_ ||
    188               text_input_type_ != ui::TEXT_INPUT_TYPE_NONE)) {
    189         StartTouchEditing();
    190         UpdateEditingController();
    191       }
    192       // fall through to reset |scroll_in_progress_|.
    193     case ui::ET_SCROLL_FLING_START:
    194       selection_gesture_in_process_ = false;
    195       scroll_in_progress_ = false;
    196       break;
    197     default:
    198       break;
    199   }
    200   return false;
    201 }
    202 
    203 void TouchEditableImplAura::GestureEventAck(int gesture_event_type) {
    204   DCHECK(rwhva_);
    205   if (gesture_event_type == blink::WebInputEvent::GestureTap &&
    206       text_input_type_ != ui::TEXT_INPUT_TYPE_NONE &&
    207       textfield_was_focused_on_tap_) {
    208     StartTouchEditing();
    209     UpdateEditingController();
    210   }
    211 }
    212 
    213 void TouchEditableImplAura::OnViewDestroyed() {
    214   Cleanup();
    215 }
    216 
    217 ////////////////////////////////////////////////////////////////////////////////
    218 // TouchEditableImplAura, ui::TouchEditable implementation:
    219 
    220 void TouchEditableImplAura::SelectRect(const gfx::Point& start,
    221                                        const gfx::Point& end) {
    222   RenderWidgetHost* host = rwhva_->GetRenderWidgetHost();
    223   RenderViewHost* rvh = RenderViewHost::From(host);
    224   WebContentsImpl* wc =
    225       static_cast<WebContentsImpl*>(WebContents::FromRenderViewHost(rvh));
    226   wc->SelectRange(start, end);
    227 }
    228 
    229 void TouchEditableImplAura::MoveCaretTo(const gfx::Point& point) {
    230   if (!rwhva_)
    231     return;
    232 
    233   RenderWidgetHostImpl* host = RenderWidgetHostImpl::From(
    234       rwhva_->GetRenderWidgetHost());
    235   host->MoveCaret(point);
    236 }
    237 
    238 void TouchEditableImplAura::GetSelectionEndPoints(gfx::Rect* p1,
    239                                                   gfx::Rect* p2) {
    240   *p1 = selection_anchor_rect_;
    241   *p2 = selection_focus_rect_;
    242 }
    243 
    244 gfx::Rect TouchEditableImplAura::GetBounds() {
    245   return rwhva_ ? gfx::Rect(rwhva_->GetNativeView()->bounds().size()) :
    246       gfx::Rect();
    247 }
    248 
    249 gfx::NativeView TouchEditableImplAura::GetNativeView() const {
    250   return rwhva_ ? rwhva_->GetNativeView()->GetToplevelWindow() : NULL;
    251 }
    252 
    253 void TouchEditableImplAura::ConvertPointToScreen(gfx::Point* point) {
    254   if (!rwhva_)
    255     return;
    256   aura::Window* window = rwhva_->GetNativeView();
    257   aura::client::ScreenPositionClient* screen_position_client =
    258       aura::client::GetScreenPositionClient(window->GetRootWindow());
    259   if (screen_position_client)
    260     screen_position_client->ConvertPointToScreen(window, point);
    261 }
    262 
    263 void TouchEditableImplAura::ConvertPointFromScreen(gfx::Point* point) {
    264   if (!rwhva_)
    265     return;
    266   aura::Window* window = rwhva_->GetNativeView();
    267   aura::client::ScreenPositionClient* screen_position_client =
    268       aura::client::GetScreenPositionClient(window->GetRootWindow());
    269   if (screen_position_client)
    270     screen_position_client->ConvertPointFromScreen(window, point);
    271 }
    272 
    273 bool TouchEditableImplAura::DrawsHandles() {
    274   return false;
    275 }
    276 
    277 void TouchEditableImplAura::OpenContextMenu(const gfx::Point& anchor) {
    278   if (!rwhva_)
    279     return;
    280   gfx::Point point = anchor;
    281   ConvertPointFromScreen(&point);
    282   RenderWidgetHost* host = rwhva_->GetRenderWidgetHost();
    283   host->Send(new ViewMsg_ShowContextMenu(
    284       host->GetRoutingID(), ui::MENU_SOURCE_TOUCH_EDIT_MENU, point));
    285   EndTouchEditing(false);
    286 }
    287 
    288 bool TouchEditableImplAura::IsCommandIdChecked(int command_id) const {
    289   NOTREACHED();
    290   return false;
    291 }
    292 
    293 bool TouchEditableImplAura::IsCommandIdEnabled(int command_id) const {
    294   if (!rwhva_)
    295     return false;
    296   bool editable = rwhva_->GetTextInputType() != ui::TEXT_INPUT_TYPE_NONE;
    297   gfx::Range selection_range;
    298   rwhva_->GetSelectionRange(&selection_range);
    299   bool has_selection = !selection_range.is_empty();
    300   switch (command_id) {
    301     case IDS_APP_CUT:
    302       return editable && has_selection;
    303     case IDS_APP_COPY:
    304       return has_selection;
    305     case IDS_APP_PASTE: {
    306       base::string16 result;
    307       ui::Clipboard::GetForCurrentThread()->ReadText(
    308           ui::CLIPBOARD_TYPE_COPY_PASTE, &result);
    309       return editable && !result.empty();
    310     }
    311     case IDS_APP_DELETE:
    312       return editable && has_selection;
    313     case IDS_APP_SELECT_ALL:
    314       return true;
    315     default:
    316       return false;
    317   }
    318 }
    319 
    320 bool TouchEditableImplAura::GetAcceleratorForCommandId(
    321     int command_id,
    322     ui::Accelerator* accelerator) {
    323   return false;
    324 }
    325 
    326 void TouchEditableImplAura::ExecuteCommand(int command_id, int event_flags) {
    327   RenderWidgetHost* host = rwhva_->GetRenderWidgetHost();
    328   RenderViewHost* rvh = RenderViewHost::From(host);
    329   WebContents* wc = WebContents::FromRenderViewHost(rvh);
    330 
    331   switch (command_id) {
    332     case IDS_APP_CUT:
    333       wc->Cut();
    334       break;
    335     case IDS_APP_COPY:
    336       wc->Copy();
    337       break;
    338     case IDS_APP_PASTE:
    339       wc->Paste();
    340       break;
    341     case IDS_APP_DELETE:
    342       wc->Delete();
    343       break;
    344     case IDS_APP_SELECT_ALL:
    345       wc->SelectAll();
    346       break;
    347     default:
    348       NOTREACHED();
    349       break;
    350   }
    351   EndTouchEditing(false);
    352 }
    353 
    354 void TouchEditableImplAura::DestroyTouchSelection() {
    355   EndTouchEditing(false);
    356 }
    357 
    358 ////////////////////////////////////////////////////////////////////////////////
    359 // TouchEditableImplAura, private:
    360 
    361 TouchEditableImplAura::TouchEditableImplAura()
    362     : text_input_type_(ui::TEXT_INPUT_TYPE_NONE),
    363       rwhva_(NULL),
    364       selection_gesture_in_process_(false),
    365       handles_hidden_due_to_scroll_(false),
    366       scroll_in_progress_(false),
    367       overscroll_in_progress_(false),
    368       textfield_was_focused_on_tap_(false) {
    369 }
    370 
    371 void TouchEditableImplAura::Cleanup() {
    372   if (rwhva_) {
    373     rwhva_->set_touch_editing_client(NULL);
    374     rwhva_ = NULL;
    375   }
    376   text_input_type_ = ui::TEXT_INPUT_TYPE_NONE;
    377   EndTouchEditing(true);
    378   selection_gesture_in_process_ = false;
    379   handles_hidden_due_to_scroll_ = false;
    380   scroll_in_progress_ = false;
    381   overscroll_in_progress_ = false;
    382 }
    383 
    384 }  // namespace content
    385