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 "ui/aura/client/screen_position_client.h"
     14 #include "ui/aura/window.h"
     15 #include "ui/aura/window_tree_host.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 #include "ui/strings/grit/ui_strings.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   scrolls_in_progress_++;
     65 }
     66 
     67 void TouchEditableImplAura::OverscrollCompleted() {
     68   ScrollEnded();
     69 }
     70 
     71 ////////////////////////////////////////////////////////////////////////////////
     72 // TouchEditableImplAura, RenderWidgetHostViewAura::TouchEditingClient
     73 // implementation:
     74 
     75 void TouchEditableImplAura::StartTouchEditing() {
     76   if (!rwhva_ || !rwhva_->HasFocus())
     77     return;
     78 
     79   if (!touch_selection_controller_) {
     80     touch_selection_controller_.reset(
     81         ui::TouchSelectionController::create(this));
     82   }
     83   if (touch_selection_controller_)
     84     touch_selection_controller_->SelectionChanged();
     85 }
     86 
     87 void TouchEditableImplAura::EndTouchEditing(bool quick) {
     88   if (touch_selection_controller_) {
     89     if (touch_selection_controller_->IsHandleDragInProgress()) {
     90       touch_selection_controller_->SelectionChanged();
     91     } else {
     92       selection_gesture_in_process_ = false;
     93       touch_selection_controller_->HideHandles(quick);
     94       touch_selection_controller_.reset();
     95     }
     96   }
     97 }
     98 
     99 void TouchEditableImplAura::OnSelectionOrCursorChanged(const gfx::Rect& anchor,
    100                                                        const gfx::Rect& focus) {
    101   selection_anchor_rect_ = anchor;
    102   selection_focus_rect_ = focus;
    103 
    104   // If touch editing handles were not visible, we bring them up only if the
    105   // current event is a gesture event, no scroll/fling/overscoll is in progress,
    106   // and there is non-zero selection on the page
    107   if (selection_gesture_in_process_ && !scrolls_in_progress_ &&
    108       selection_anchor_rect_ != selection_focus_rect_) {
    109     StartTouchEditing();
    110     selection_gesture_in_process_ = false;
    111   }
    112 
    113   UpdateEditingController();
    114 }
    115 
    116 void TouchEditableImplAura::OnTextInputTypeChanged(ui::TextInputType type) {
    117   text_input_type_ = type;
    118 }
    119 
    120 bool TouchEditableImplAura::HandleInputEvent(const ui::Event* event) {
    121   DCHECK(rwhva_);
    122   if (!event->IsGestureEvent()) {
    123     // Ignore all non-gesture events. Non-gesture events that can deactivate
    124     // touch editing are handled in TouchSelectionControllerImpl.
    125     return false;
    126   }
    127 
    128   const ui::GestureEvent* gesture_event =
    129       static_cast<const ui::GestureEvent*>(event);
    130   switch (event->type()) {
    131     case ui::ET_GESTURE_TAP:
    132       // When the user taps, we want to show touch editing handles if user
    133       // tapped on selected text.
    134       if (gesture_event->details().tap_count() == 1 &&
    135           selection_anchor_rect_ != selection_focus_rect_) {
    136         // UnionRects only works for rects with non-zero width.
    137         gfx::Rect anchor(selection_anchor_rect_.origin(),
    138                          gfx::Size(1, selection_anchor_rect_.height()));
    139         gfx::Rect focus(selection_focus_rect_.origin(),
    140                         gfx::Size(1, selection_focus_rect_.height()));
    141         gfx::Rect selection_rect = gfx::UnionRects(anchor, focus);
    142         if (selection_rect.Contains(gesture_event->location())) {
    143           StartTouchEditing();
    144           return true;
    145         }
    146       }
    147       // For single taps, not inside selected region, we want to show handles
    148       // only when the tap is on an already focused textfield.
    149       textfield_was_focused_on_tap_ =
    150           gesture_event->details().tap_count() == 1 &&
    151           text_input_type_ != ui::TEXT_INPUT_TYPE_NONE;
    152       break;
    153     case ui::ET_GESTURE_LONG_PRESS:
    154       selection_gesture_in_process_ = true;
    155       break;
    156     case ui::ET_GESTURE_SCROLL_BEGIN:
    157       scrolls_in_progress_++;
    158       // We need to hide selection handles during scroll (including fling and
    159       // overscroll), but they should be re-activated after scrolling if:
    160       //  - an existing scroll decided that handles should be shown after
    161       //    scrolling; or
    162       //  - the gesture in progress is going to end in selection; or
    163       //  - selection handles are currently active.
    164       handles_hidden_due_to_scroll_ = handles_hidden_due_to_scroll_ ||
    165                                       selection_gesture_in_process_ ||
    166                                       touch_selection_controller_ != NULL;
    167       selection_gesture_in_process_ = false;
    168       EndTouchEditing(true);
    169       break;
    170     case ui::ET_GESTURE_SCROLL_END:
    171       ScrollEnded();
    172       break;
    173     default:
    174       break;
    175   }
    176   return false;
    177 }
    178 
    179 void TouchEditableImplAura::GestureEventAck(int gesture_event_type) {
    180   DCHECK(rwhva_);
    181   if (gesture_event_type == blink::WebInputEvent::GestureTap &&
    182       text_input_type_ != ui::TEXT_INPUT_TYPE_NONE &&
    183       textfield_was_focused_on_tap_) {
    184     StartTouchEditing();
    185     UpdateEditingController();
    186   }
    187 }
    188 
    189 void TouchEditableImplAura::DidStopFlinging() {
    190   ScrollEnded();
    191 }
    192 
    193 void TouchEditableImplAura::OnViewDestroyed() {
    194   Cleanup();
    195 }
    196 
    197 ////////////////////////////////////////////////////////////////////////////////
    198 // TouchEditableImplAura, ui::TouchEditable implementation:
    199 
    200 void TouchEditableImplAura::SelectRect(const gfx::Point& start,
    201                                        const gfx::Point& end) {
    202   RenderWidgetHost* host = rwhva_->GetRenderWidgetHost();
    203   RenderViewHost* rvh = RenderViewHost::From(host);
    204   WebContentsImpl* wc =
    205       static_cast<WebContentsImpl*>(WebContents::FromRenderViewHost(rvh));
    206   wc->SelectRange(start, end);
    207 }
    208 
    209 void TouchEditableImplAura::MoveCaretTo(const gfx::Point& point) {
    210   if (!rwhva_)
    211     return;
    212 
    213   RenderWidgetHostImpl* host = RenderWidgetHostImpl::From(
    214       rwhva_->GetRenderWidgetHost());
    215   host->MoveCaret(point);
    216 }
    217 
    218 void TouchEditableImplAura::GetSelectionEndPoints(gfx::Rect* p1,
    219                                                   gfx::Rect* p2) {
    220   *p1 = selection_anchor_rect_;
    221   *p2 = selection_focus_rect_;
    222 }
    223 
    224 gfx::Rect TouchEditableImplAura::GetBounds() {
    225   return rwhva_ ? gfx::Rect(rwhva_->GetNativeView()->bounds().size()) :
    226       gfx::Rect();
    227 }
    228 
    229 gfx::NativeView TouchEditableImplAura::GetNativeView() const {
    230   return rwhva_ ? rwhva_->GetNativeView()->GetToplevelWindow() : NULL;
    231 }
    232 
    233 void TouchEditableImplAura::ConvertPointToScreen(gfx::Point* point) {
    234   if (!rwhva_)
    235     return;
    236   aura::Window* window = rwhva_->GetNativeView();
    237   aura::client::ScreenPositionClient* screen_position_client =
    238       aura::client::GetScreenPositionClient(window->GetRootWindow());
    239   if (screen_position_client)
    240     screen_position_client->ConvertPointToScreen(window, point);
    241 }
    242 
    243 void TouchEditableImplAura::ConvertPointFromScreen(gfx::Point* point) {
    244   if (!rwhva_)
    245     return;
    246   aura::Window* window = rwhva_->GetNativeView();
    247   aura::client::ScreenPositionClient* screen_position_client =
    248       aura::client::GetScreenPositionClient(window->GetRootWindow());
    249   if (screen_position_client)
    250     screen_position_client->ConvertPointFromScreen(window, point);
    251 }
    252 
    253 bool TouchEditableImplAura::DrawsHandles() {
    254   return false;
    255 }
    256 
    257 void TouchEditableImplAura::OpenContextMenu(const gfx::Point& anchor) {
    258   if (!rwhva_)
    259     return;
    260   gfx::Point point = anchor;
    261   ConvertPointFromScreen(&point);
    262   RenderWidgetHost* host = rwhva_->GetRenderWidgetHost();
    263   host->Send(new ViewMsg_ShowContextMenu(
    264       host->GetRoutingID(), ui::MENU_SOURCE_TOUCH_EDIT_MENU, point));
    265   EndTouchEditing(false);
    266 }
    267 
    268 bool TouchEditableImplAura::IsCommandIdChecked(int command_id) const {
    269   NOTREACHED();
    270   return false;
    271 }
    272 
    273 bool TouchEditableImplAura::IsCommandIdEnabled(int command_id) const {
    274   if (!rwhva_)
    275     return false;
    276   bool editable = rwhva_->GetTextInputType() != ui::TEXT_INPUT_TYPE_NONE;
    277   bool readable = rwhva_->GetTextInputType() != ui::TEXT_INPUT_TYPE_PASSWORD;
    278   gfx::Range selection_range;
    279   rwhva_->GetSelectionRange(&selection_range);
    280   bool has_selection = !selection_range.is_empty();
    281   switch (command_id) {
    282     case IDS_APP_CUT:
    283       return editable && readable && has_selection;
    284     case IDS_APP_COPY:
    285       return readable && has_selection;
    286     case IDS_APP_PASTE: {
    287       base::string16 result;
    288       ui::Clipboard::GetForCurrentThread()->ReadText(
    289           ui::CLIPBOARD_TYPE_COPY_PASTE, &result);
    290       return editable && !result.empty();
    291     }
    292     case IDS_APP_DELETE:
    293       return editable && has_selection;
    294     case IDS_APP_SELECT_ALL:
    295       return true;
    296     default:
    297       return false;
    298   }
    299 }
    300 
    301 bool TouchEditableImplAura::GetAcceleratorForCommandId(
    302     int command_id,
    303     ui::Accelerator* accelerator) {
    304   return false;
    305 }
    306 
    307 void TouchEditableImplAura::ExecuteCommand(int command_id, int event_flags) {
    308   RenderWidgetHost* host = rwhva_->GetRenderWidgetHost();
    309   RenderViewHost* rvh = RenderViewHost::From(host);
    310   WebContents* wc = WebContents::FromRenderViewHost(rvh);
    311 
    312   switch (command_id) {
    313     case IDS_APP_CUT:
    314       wc->Cut();
    315       break;
    316     case IDS_APP_COPY:
    317       wc->Copy();
    318       break;
    319     case IDS_APP_PASTE:
    320       wc->Paste();
    321       break;
    322     case IDS_APP_DELETE:
    323       wc->Delete();
    324       break;
    325     case IDS_APP_SELECT_ALL:
    326       wc->SelectAll();
    327       break;
    328     default:
    329       NOTREACHED();
    330       break;
    331   }
    332   EndTouchEditing(false);
    333 }
    334 
    335 void TouchEditableImplAura::DestroyTouchSelection() {
    336   EndTouchEditing(false);
    337 }
    338 
    339 ////////////////////////////////////////////////////////////////////////////////
    340 // TouchEditableImplAura, private:
    341 
    342 TouchEditableImplAura::TouchEditableImplAura()
    343     : text_input_type_(ui::TEXT_INPUT_TYPE_NONE),
    344       rwhva_(NULL),
    345       selection_gesture_in_process_(false),
    346       handles_hidden_due_to_scroll_(false),
    347       scrolls_in_progress_(0),
    348       textfield_was_focused_on_tap_(false) {
    349 }
    350 
    351 void TouchEditableImplAura::ScrollEnded() {
    352   scrolls_in_progress_--;
    353   // If there is no scrolling left in progress, show selection handles if they
    354   // were hidden due to scroll and there is a selection.
    355   if (!scrolls_in_progress_ && handles_hidden_due_to_scroll_ &&
    356       (selection_anchor_rect_ != selection_focus_rect_ ||
    357           text_input_type_ != ui::TEXT_INPUT_TYPE_NONE)) {
    358     StartTouchEditing();
    359     UpdateEditingController();
    360     handles_hidden_due_to_scroll_ = false;
    361   }
    362 }
    363 
    364 void TouchEditableImplAura::Cleanup() {
    365   if (rwhva_) {
    366     rwhva_->set_touch_editing_client(NULL);
    367     rwhva_ = NULL;
    368   }
    369   text_input_type_ = ui::TEXT_INPUT_TYPE_NONE;
    370   EndTouchEditing(true);
    371   selection_gesture_in_process_ = false;
    372   handles_hidden_due_to_scroll_ = false;
    373   scrolls_in_progress_ = 0;
    374 }
    375 
    376 }  // namespace content
    377