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