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/range/range.h"
     18 #include "ui/base/ui_base_switches_util.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 (!touch_selection_controller_) {
     98     touch_selection_controller_.reset(
     99         ui::TouchSelectionController::create(this));
    100   }
    101   if (touch_selection_controller_)
    102     touch_selection_controller_->SelectionChanged();
    103 }
    104 
    105 void TouchEditableImplAura::EndTouchEditing() {
    106   if (touch_selection_controller_) {
    107     if (touch_selection_controller_->IsHandleDragInProgress())
    108       touch_selection_controller_->SelectionChanged();
    109     else
    110       touch_selection_controller_.reset();
    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   UpdateEditingController();
    119 }
    120 
    121 void TouchEditableImplAura::OnTextInputTypeChanged(ui::TextInputType type) {
    122   text_input_type_ = type;
    123 }
    124 
    125 bool TouchEditableImplAura::HandleInputEvent(const ui::Event* event) {
    126   DCHECK(rwhva_);
    127   if (event->IsTouchEvent())
    128     return false;
    129 
    130   if (!event->IsGestureEvent()) {
    131     EndTouchEditing();
    132     return false;
    133   }
    134 
    135   const ui::GestureEvent* gesture_event =
    136       static_cast<const ui::GestureEvent*>(event);
    137   switch (event->type()) {
    138     case ui::ET_GESTURE_TAP:
    139       tap_gesture_tap_count_queue_.push(gesture_event->details().tap_count());
    140       if (gesture_event->details().tap_count() > 1)
    141         selection_gesture_in_process_ = true;
    142       // When the user taps, we want to show touch editing handles if user
    143       // tapped on selected text.
    144       if (selection_anchor_rect_ != selection_focus_rect_) {
    145         // UnionRects only works for rects with non-zero width.
    146         gfx::Rect anchor(selection_anchor_rect_.origin(),
    147                          gfx::Size(1, selection_anchor_rect_.height()));
    148         gfx::Rect focus(selection_focus_rect_.origin(),
    149                         gfx::Size(1, selection_focus_rect_.height()));
    150         gfx::Rect selection_rect = gfx::UnionRects(anchor, focus);
    151         if (selection_rect.Contains(gesture_event->location())) {
    152           StartTouchEditing();
    153           return true;
    154         }
    155       }
    156       // For single taps, not inside selected region, we want to show handles
    157       // only when the tap is on an already focused textfield.
    158       is_tap_on_focused_textfield_ = false;
    159       if (gesture_event->details().tap_count() == 1 &&
    160           text_input_type_ != ui::TEXT_INPUT_TYPE_NONE)
    161         is_tap_on_focused_textfield_ = true;
    162       break;
    163     case ui::ET_GESTURE_LONG_PRESS:
    164       selection_gesture_in_process_ = true;
    165       break;
    166     case ui::ET_GESTURE_SCROLL_BEGIN:
    167       // If selection handles are currently visible, we want to get them back up
    168       // when scrolling ends. So we set |handles_hidden_due_to_scroll_| so that
    169       // we can re-start touch editing when we call |UpdateEditingController()|
    170       // on scroll end gesture.
    171       scroll_in_progress_ = true;
    172       handles_hidden_due_to_scroll_ = false;
    173       if (touch_selection_controller_)
    174         handles_hidden_due_to_scroll_ = true;
    175       EndTouchEditing();
    176       break;
    177     case ui::ET_GESTURE_SCROLL_END:
    178       // Scroll has ended, but we might still be in overscroll animation.
    179       if (handles_hidden_due_to_scroll_ && !overscroll_in_progress_ &&
    180           (selection_anchor_rect_ != selection_focus_rect_ ||
    181               text_input_type_ != ui::TEXT_INPUT_TYPE_NONE)) {
    182         StartTouchEditing();
    183         UpdateEditingController();
    184       }
    185       // fall through to reset |scroll_in_progress_|.
    186     case ui::ET_SCROLL_FLING_START:
    187       selection_gesture_in_process_ = false;
    188       scroll_in_progress_ = false;
    189       break;
    190     default:
    191       break;
    192   }
    193   return false;
    194 }
    195 
    196 void TouchEditableImplAura::GestureEventAck(int gesture_event_type) {
    197   DCHECK(rwhva_);
    198   if (gesture_event_type == WebKit::WebInputEvent::GestureTap &&
    199       text_input_type_ != ui::TEXT_INPUT_TYPE_NONE &&
    200       is_tap_on_focused_textfield_) {
    201     StartTouchEditing();
    202     if (touch_selection_controller_)
    203       touch_selection_controller_->SelectionChanged();
    204   }
    205 
    206   if (gesture_event_type == WebKit::WebInputEvent::GestureLongPress)
    207     selection_gesture_in_process_ = false;
    208   if (gesture_event_type == WebKit::WebInputEvent::GestureTap) {
    209     if (tap_gesture_tap_count_queue_.front() > 1)
    210       selection_gesture_in_process_ = false;
    211     tap_gesture_tap_count_queue_.pop();
    212   }
    213 }
    214 
    215 void TouchEditableImplAura::OnViewDestroyed() {
    216   Cleanup();
    217 }
    218 
    219 ////////////////////////////////////////////////////////////////////////////////
    220 // TouchEditableImplAura, ui::TouchEditable implementation:
    221 
    222 void TouchEditableImplAura::SelectRect(const gfx::Point& start,
    223                                        const gfx::Point& end) {
    224   if (!rwhva_)
    225     return;
    226 
    227   RenderWidgetHostImpl* host = RenderWidgetHostImpl::From(
    228       rwhva_->GetRenderWidgetHost());
    229   host->SelectRange(start, end);
    230 }
    231 
    232 void TouchEditableImplAura::MoveCaretTo(const gfx::Point& point) {
    233   if (!rwhva_)
    234     return;
    235 
    236   RenderWidgetHostImpl* host = RenderWidgetHostImpl::From(
    237       rwhva_->GetRenderWidgetHost());
    238   host->MoveCaret(point);
    239 }
    240 
    241 void TouchEditableImplAura::GetSelectionEndPoints(gfx::Rect* p1,
    242                                                   gfx::Rect* p2) {
    243   *p1 = selection_anchor_rect_;
    244   *p2 = selection_focus_rect_;
    245 }
    246 
    247 gfx::Rect TouchEditableImplAura::GetBounds() {
    248   return rwhva_ ? rwhva_->GetNativeView()->bounds() : gfx::Rect();
    249 }
    250 
    251 gfx::NativeView TouchEditableImplAura::GetNativeView() {
    252   return rwhva_ ? rwhva_->GetNativeView()->GetRootWindow() : NULL;
    253 }
    254 
    255 void TouchEditableImplAura::ConvertPointToScreen(gfx::Point* point) {
    256   if (!rwhva_)
    257     return;
    258   aura::Window* window = rwhva_->GetNativeView();
    259   aura::client::ScreenPositionClient* screen_position_client =
    260       aura::client::GetScreenPositionClient(window->GetRootWindow());
    261   if (screen_position_client)
    262     screen_position_client->ConvertPointToScreen(window, point);
    263 }
    264 
    265 void TouchEditableImplAura::ConvertPointFromScreen(gfx::Point* point) {
    266   if (!rwhva_)
    267     return;
    268   aura::Window* window = rwhva_->GetNativeView();
    269   aura::client::ScreenPositionClient* screen_position_client =
    270       aura::client::GetScreenPositionClient(window->GetRootWindow());
    271   if (screen_position_client)
    272     screen_position_client->ConvertPointFromScreen(window, point);
    273 }
    274 
    275 bool TouchEditableImplAura::DrawsHandles() {
    276   return false;
    277 }
    278 
    279 void TouchEditableImplAura::OpenContextMenu(const gfx::Point& anchor) {
    280   if (!rwhva_)
    281     return;
    282   gfx::Point point = anchor;
    283   ConvertPointFromScreen(&point);
    284   RenderWidgetHost* host = rwhva_->GetRenderWidgetHost();
    285   host->Send(new ViewMsg_ShowContextMenu(host->GetRoutingID(), point));
    286   EndTouchEditing();
    287 }
    288 
    289 bool TouchEditableImplAura::IsCommandIdChecked(int command_id) const {
    290   NOTREACHED();
    291   return false;
    292 }
    293 
    294 bool TouchEditableImplAura::IsCommandIdEnabled(int command_id) const {
    295   if (!rwhva_)
    296     return false;
    297   bool editable = rwhva_->GetTextInputType() != ui::TEXT_INPUT_TYPE_NONE;
    298   ui::Range selection_range;
    299   rwhva_->GetSelectionRange(&selection_range);
    300   bool has_selection = !selection_range.is_empty();
    301   switch (command_id) {
    302     case IDS_APP_CUT:
    303       return editable && has_selection;
    304     case IDS_APP_COPY:
    305       return has_selection;
    306     case IDS_APP_PASTE: {
    307       string16 result;
    308       ui::Clipboard::GetForCurrentThread()->ReadText(
    309           ui::Clipboard::BUFFER_STANDARD, &result);
    310       return editable && !result.empty();
    311     }
    312     case IDS_APP_DELETE:
    313       return editable && has_selection;
    314     case IDS_APP_SELECT_ALL:
    315       return true;
    316     default:
    317       return false;
    318   }
    319 }
    320 
    321 bool TouchEditableImplAura::GetAcceleratorForCommandId(
    322     int command_id,
    323     ui::Accelerator* accelerator) {
    324   return false;
    325 }
    326 
    327 void TouchEditableImplAura::ExecuteCommand(int command_id, int event_flags) {
    328   if (!rwhva_)
    329     return;
    330   RenderWidgetHost* host = rwhva_->GetRenderWidgetHost();
    331   switch (command_id) {
    332     case IDS_APP_CUT:
    333       host->Cut();
    334       break;
    335     case IDS_APP_COPY:
    336       host->Copy();
    337       break;
    338     case IDS_APP_PASTE:
    339       host->Paste();
    340       break;
    341     case IDS_APP_DELETE:
    342       host->Delete();
    343       break;
    344     case IDS_APP_SELECT_ALL:
    345       host->SelectAll();
    346       break;
    347     default:
    348       NOTREACHED();
    349       break;
    350   }
    351   EndTouchEditing();
    352 }
    353 
    354 ////////////////////////////////////////////////////////////////////////////////
    355 // TouchEditableImplAura, private:
    356 
    357 TouchEditableImplAura::TouchEditableImplAura()
    358     : text_input_type_(ui::TEXT_INPUT_TYPE_NONE),
    359       rwhva_(NULL),
    360       selection_gesture_in_process_(false),
    361       handles_hidden_due_to_scroll_(false),
    362       scroll_in_progress_(false),
    363       overscroll_in_progress_(false),
    364       is_tap_on_focused_textfield_(false) {
    365 }
    366 
    367 void TouchEditableImplAura::Cleanup() {
    368   if (rwhva_) {
    369     rwhva_->set_touch_editing_client(NULL);
    370     rwhva_ = NULL;
    371   }
    372   text_input_type_ = ui::TEXT_INPUT_TYPE_NONE;
    373   touch_selection_controller_.reset();
    374   handles_hidden_due_to_scroll_ = false;
    375   scroll_in_progress_ = false;
    376   overscroll_in_progress_ = false;
    377 }
    378 
    379 }  // namespace content
    380