Home | History | Annotate | Download | only in views
      1 // Copyright 2014 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 "base/strings/utf_string_conversions.h"
      6 #include "chrome/browser/ui/views/menu_test_base.h"
      7 #include "chrome/test/base/interactive_test_utils.h"
      8 #include "ui/base/dragdrop/drag_drop_types.h"
      9 #include "ui/base/dragdrop/os_exchange_data.h"
     10 #include "ui/views/controls/menu/menu_controller.h"
     11 #include "ui/views/controls/menu/menu_item_view.h"
     12 #include "ui/views/controls/menu/menu_runner.h"
     13 #include "ui/views/controls/menu/submenu_view.h"
     14 #include "ui/views/view.h"
     15 
     16 namespace {
     17 
     18 // Borrowed from chrome/browser/ui/views/bookmarks/bookmark_bar_view_test.cc,
     19 // since these are also disabled on Linux for drag and drop.
     20 // TODO(erg): Fix DND tests on linux_aura. crbug.com/163931
     21 #if defined(OS_LINUX) && defined(USE_AURA)
     22 #define MAYBE(x) DISABLED_##x
     23 #else
     24 #define MAYBE(x) x
     25 #endif
     26 
     27 const char kTestNestedDragData[] = "test_nested_drag_data";
     28 const char kTestTopLevelDragData[] = "test_top_level_drag_data";
     29 
     30 // A simple view which can be dragged.
     31 class TestDragView : public views::View {
     32  public:
     33   TestDragView();
     34   virtual ~TestDragView();
     35 
     36  private:
     37   // views::View:
     38   virtual int GetDragOperations(const gfx::Point& point) OVERRIDE;
     39   virtual void WriteDragData(const gfx::Point& point,
     40                              ui::OSExchangeData* data) OVERRIDE;
     41 
     42   DISALLOW_COPY_AND_ASSIGN(TestDragView);
     43 };
     44 
     45 TestDragView::TestDragView() {
     46 }
     47 
     48 TestDragView::~TestDragView() {
     49 }
     50 
     51 int TestDragView::GetDragOperations(const gfx::Point& point) {
     52   return ui::DragDropTypes::DRAG_MOVE;
     53 }
     54 
     55 void TestDragView::WriteDragData(const gfx::Point& point,
     56                                  ui::OSExchangeData* data) {
     57   data->SetString(base::ASCIIToUTF16(kTestNestedDragData));
     58 }
     59 
     60 // A simple view to serve as a drop target.
     61 class TestTargetView : public views::View {
     62  public:
     63   TestTargetView();
     64   virtual ~TestTargetView();
     65 
     66   // Initializes this view to have the same bounds as |parent| and two draggable
     67   // child views.
     68   void Init(views::View* parent);
     69   bool dragging() const { return dragging_; }
     70   bool dropped() const { return dropped_; }
     71 
     72  private:
     73   // views::View:
     74   virtual bool GetDropFormats(
     75       int* formats,
     76       std::set<OSExchangeData::CustomFormat>* custom_formats) OVERRIDE;
     77   virtual bool AreDropTypesRequired() OVERRIDE;
     78   virtual bool CanDrop(const OSExchangeData& data) OVERRIDE;
     79   virtual void OnDragEntered(const ui::DropTargetEvent& event) OVERRIDE;
     80   virtual int OnDragUpdated(const ui::DropTargetEvent& event) OVERRIDE;
     81   virtual int OnPerformDrop(const ui::DropTargetEvent& event) OVERRIDE;
     82   virtual void OnDragExited() OVERRIDE;
     83 
     84   // Whether or not we are currently dragging.
     85   bool dragging_;
     86 
     87   // Whether or not a drop has been performed on the view.
     88   bool dropped_;
     89 
     90   DISALLOW_COPY_AND_ASSIGN(TestTargetView);
     91 };
     92 
     93 TestTargetView::TestTargetView() : dragging_(false), dropped_(false) {
     94 }
     95 
     96 void TestTargetView::Init(views::View* parent) {
     97   // First, match the parent's size.
     98   SetSize(parent->size());
     99 
    100   // Then add two draggable views, each 10x2.
    101   views::View* first = new TestDragView();
    102   AddChildView(first);
    103   first->SetBounds(2, 2, 10, 2);
    104 
    105   views::View* second = new TestDragView();
    106   AddChildView(second);
    107   second->SetBounds(15, 2, 10, 2);
    108 }
    109 
    110 TestTargetView::~TestTargetView() {
    111 }
    112 
    113 bool TestTargetView::GetDropFormats(
    114     int* formats, std::set<OSExchangeData::CustomFormat>* custom_formats) {
    115   *formats = ui::OSExchangeData::STRING;
    116   return true;
    117 }
    118 
    119 bool TestTargetView::AreDropTypesRequired() {
    120   return true;
    121 }
    122 
    123 bool TestTargetView::CanDrop(const OSExchangeData& data) {
    124   base::string16 contents;
    125   return data.GetString(&contents) &&
    126          contents == base::ASCIIToUTF16(kTestNestedDragData);
    127 }
    128 
    129 void TestTargetView::OnDragEntered(const ui::DropTargetEvent& event) {
    130   dragging_ = true;
    131 }
    132 
    133 int TestTargetView::OnDragUpdated(const ui::DropTargetEvent& event) {
    134   return ui::DragDropTypes::DRAG_MOVE;
    135 }
    136 
    137 int TestTargetView::OnPerformDrop(const ui::DropTargetEvent& event) {
    138   dragging_ = false;
    139   dropped_ = true;
    140   return ui::DragDropTypes::DRAG_MOVE;
    141 }
    142 
    143 void TestTargetView::OnDragExited() {
    144   dragging_ = false;
    145 }
    146 
    147 }  // namespace
    148 
    149 class MenuViewDragAndDropTest : public MenuTestBase {
    150  public:
    151   MenuViewDragAndDropTest();
    152   virtual ~MenuViewDragAndDropTest();
    153 
    154  protected:
    155   TestTargetView* target_view() { return target_view_; }
    156   bool asked_to_close() const { return asked_to_close_; }
    157   bool performed_in_menu_drop() const { return performed_in_menu_drop_; }
    158 
    159  private:
    160   // MenuTestBase:
    161   virtual void BuildMenu(views::MenuItemView* menu) OVERRIDE;
    162 
    163   // views::MenuDelegate:
    164   virtual bool GetDropFormats(
    165       views::MenuItemView* menu,
    166       int* formats,
    167       std::set<ui::OSExchangeData::CustomFormat>* custom_formats) OVERRIDE;
    168   virtual bool AreDropTypesRequired(views::MenuItemView* menu) OVERRIDE;
    169   virtual bool CanDrop(views::MenuItemView* menu,
    170                        const ui::OSExchangeData& data) OVERRIDE;
    171   virtual int GetDropOperation(views::MenuItemView* item,
    172                                const ui::DropTargetEvent& event,
    173                                DropPosition* position) OVERRIDE;
    174   virtual int OnPerformDrop(views::MenuItemView* menu,
    175                             DropPosition position,
    176                             const ui::DropTargetEvent& event) OVERRIDE;
    177   virtual bool CanDrag(views::MenuItemView* menu) OVERRIDE;
    178   virtual void WriteDragData(views::MenuItemView* sender,
    179                              ui::OSExchangeData* data) OVERRIDE;
    180   virtual int GetDragOperations(views::MenuItemView* sender) OVERRIDE;
    181   virtual bool ShouldCloseOnDragComplete() OVERRIDE;
    182 
    183   // The special view in the menu, which supports its own drag and drop.
    184   TestTargetView* target_view_;
    185 
    186   // Whether or not we have been asked to close on drag complete.
    187   bool asked_to_close_;
    188 
    189   // Whether or not a drop was performed in-menu (i.e., not including drops
    190   // in separate child views).
    191   bool performed_in_menu_drop_;
    192 
    193   DISALLOW_COPY_AND_ASSIGN(MenuViewDragAndDropTest);
    194 };
    195 
    196 MenuViewDragAndDropTest::MenuViewDragAndDropTest()
    197     : target_view_(NULL),
    198       asked_to_close_(false),
    199       performed_in_menu_drop_(false) {
    200 }
    201 
    202 MenuViewDragAndDropTest::~MenuViewDragAndDropTest() {
    203 }
    204 
    205 void MenuViewDragAndDropTest::BuildMenu(views::MenuItemView* menu) {
    206   // Build a menu item that has a nested view that supports its own drag and
    207   // drop...
    208   views::MenuItemView* menu_item_view =
    209       menu->AppendMenuItem(1,
    210                            base::ASCIIToUTF16("item 1"),
    211                            views::MenuItemView::NORMAL);
    212   target_view_ = new TestTargetView();
    213   menu_item_view->AddChildView(target_view_);
    214   // ... as well as two other, normal items.
    215   menu->AppendMenuItemWithLabel(2, base::ASCIIToUTF16("item 2"));
    216   menu->AppendMenuItemWithLabel(3, base::ASCIIToUTF16("item 3"));
    217 }
    218 
    219 bool MenuViewDragAndDropTest::GetDropFormats(
    220     views::MenuItemView* menu,
    221     int* formats,
    222     std::set<ui::OSExchangeData::CustomFormat>* custom_formats) {
    223   *formats = ui::OSExchangeData::STRING;
    224   return true;
    225 }
    226 
    227 bool MenuViewDragAndDropTest::AreDropTypesRequired(views::MenuItemView* menu) {
    228   return true;
    229 }
    230 
    231 bool MenuViewDragAndDropTest::CanDrop(views::MenuItemView* menu,
    232                                       const ui::OSExchangeData& data) {
    233   base::string16 contents;
    234   return data.GetString(&contents) &&
    235          contents == base::ASCIIToUTF16(kTestTopLevelDragData);
    236 }
    237 
    238 int MenuViewDragAndDropTest::GetDropOperation(views::MenuItemView* item,
    239                                               const ui::DropTargetEvent& event,
    240                                               DropPosition* position) {
    241   return ui::DragDropTypes::DRAG_MOVE;
    242 }
    243 
    244 
    245 int MenuViewDragAndDropTest::OnPerformDrop(views::MenuItemView* menu,
    246                                            DropPosition position,
    247                                            const ui::DropTargetEvent& event) {
    248   performed_in_menu_drop_ = true;
    249   return ui::DragDropTypes::DRAG_MOVE;
    250 }
    251 
    252 bool MenuViewDragAndDropTest::CanDrag(views::MenuItemView* menu) {
    253   return true;
    254 }
    255 
    256 void MenuViewDragAndDropTest::WriteDragData(
    257     views::MenuItemView* sender, ui::OSExchangeData* data) {
    258   data->SetString(base::ASCIIToUTF16(kTestTopLevelDragData));
    259 }
    260 
    261 int MenuViewDragAndDropTest::GetDragOperations(views::MenuItemView* sender) {
    262   return ui::DragDropTypes::DRAG_MOVE;
    263 }
    264 
    265 bool MenuViewDragAndDropTest::ShouldCloseOnDragComplete() {
    266   asked_to_close_ = true;
    267   return false;
    268 }
    269 
    270 class MenuViewDragAndDropTestTestInMenuDrag : public MenuViewDragAndDropTest {
    271  public:
    272   MenuViewDragAndDropTestTestInMenuDrag() {}
    273   virtual ~MenuViewDragAndDropTestTestInMenuDrag() {}
    274 
    275  private:
    276   // MenuViewDragAndDropTest:
    277   virtual void DoTestWithMenuOpen() OVERRIDE;
    278 
    279   void Step2();
    280   void Step3();
    281   void Step4();
    282 };
    283 
    284 void MenuViewDragAndDropTestTestInMenuDrag::DoTestWithMenuOpen() {
    285   // A few sanity checks to make sure the menu built correctly.
    286   views::SubmenuView* submenu = menu()->GetSubmenu();
    287   ASSERT_TRUE(submenu);
    288   ASSERT_TRUE(submenu->IsShowing());
    289   ASSERT_EQ(3, submenu->GetMenuItemCount());
    290 
    291   // We do this here (instead of in BuildMenu()) so that the menu is already
    292   // built and the bounds are correct.
    293   target_view()->Init(submenu->GetMenuItemAt(0));
    294 
    295   // We're going to drag the second menu element.
    296   views::MenuItemView* drag_view = submenu->GetMenuItemAt(1);
    297   ASSERT_TRUE(drag_view != NULL);
    298 
    299   // Move mouse to center of menu and press button.
    300   ui_test_utils::MoveMouseToCenterAndPress(
    301       drag_view,
    302       ui_controls::LEFT,
    303       ui_controls::DOWN,
    304       CreateEventTask(this, &MenuViewDragAndDropTestTestInMenuDrag::Step2));
    305 }
    306 
    307 void MenuViewDragAndDropTestTestInMenuDrag::Step2() {
    308   views::MenuItemView* drop_target = menu()->GetSubmenu()->GetMenuItemAt(2);
    309   gfx::Point loc(1, drop_target->height() - 1);
    310   views::View::ConvertPointToScreen(drop_target, &loc);
    311 
    312   // Start a drag.
    313   ui_controls::SendMouseMoveNotifyWhenDone(
    314       loc.x() + 10,
    315       loc.y(),
    316       CreateEventTask(this, &MenuViewDragAndDropTestTestInMenuDrag::Step3));
    317 
    318   ScheduleMouseMoveInBackground(loc.x(), loc.y());
    319 }
    320 
    321 void MenuViewDragAndDropTestTestInMenuDrag::Step3() {
    322   // Drop the item on the target.
    323   views::MenuItemView* drop_target = menu()->GetSubmenu()->GetMenuItemAt(2);
    324   gfx::Point loc(1, drop_target->height() - 2);
    325   views::View::ConvertPointToScreen(drop_target, &loc);
    326   ui_controls::SendMouseMove(loc.x(), loc.y());
    327 
    328   ui_controls::SendMouseEventsNotifyWhenDone(
    329       ui_controls::LEFT,
    330       ui_controls::UP,
    331       CreateEventTask(this, &MenuViewDragAndDropTestTestInMenuDrag::Step4));
    332 }
    333 
    334 void MenuViewDragAndDropTestTestInMenuDrag::Step4() {
    335   // Verify our state.
    336   // We should have performed an in-menu drop, and the nested view should not
    337   // have had a drag and drop. Since the drag happened in menu code, the
    338   // delegate should not have been asked whether or not to close, and the menu
    339   // should simply be closed.
    340   EXPECT_TRUE(performed_in_menu_drop());
    341   EXPECT_FALSE(target_view()->dropped());
    342   EXPECT_FALSE(asked_to_close());
    343   EXPECT_FALSE(menu()->GetSubmenu()->IsShowing());
    344 
    345   Done();
    346 }
    347 
    348 // Test that an in-menu (i.e., entirely implemented in the menu code) closes the
    349 // menu automatically once the drag is complete, and does not ask the delegate
    350 // to stay open.
    351 #if !defined(OS_WIN)  // flaky http://crbug.com/401226
    352 VIEW_TEST(MenuViewDragAndDropTestTestInMenuDrag, MAYBE(TestInMenuDrag))
    353 #endif
    354 
    355 class MenuViewDragAndDropTestNestedDrag : public MenuViewDragAndDropTest {
    356  public:
    357   MenuViewDragAndDropTestNestedDrag() {}
    358   virtual ~MenuViewDragAndDropTestNestedDrag() {}
    359 
    360  private:
    361   // MenuViewDragAndDropTest:
    362   virtual void DoTestWithMenuOpen() OVERRIDE;
    363 
    364   void Step2();
    365   void Step3();
    366   void Step4();
    367 };
    368 
    369 void MenuViewDragAndDropTestNestedDrag::DoTestWithMenuOpen() {
    370   // Sanity checks: We should be showing the menu, it should have three
    371   // children, and the first of those children should have a nested view of the
    372   // TestTargetView.
    373   views::SubmenuView* submenu = menu()->GetSubmenu();
    374   ASSERT_TRUE(submenu);
    375   ASSERT_TRUE(submenu->IsShowing());
    376   ASSERT_EQ(3, submenu->GetMenuItemCount());
    377   views::View* first_view = submenu->GetMenuItemAt(0);
    378   ASSERT_EQ(1, first_view->child_count());
    379   views::View* child_view = first_view->child_at(0);
    380   ASSERT_EQ(child_view, target_view());
    381 
    382   // We do this here (instead of in BuildMenu()) so that the menu is already
    383   // built and the bounds are correct.
    384   target_view()->Init(submenu->GetMenuItemAt(0));
    385 
    386   // The target view should now have two children.
    387   ASSERT_EQ(2, target_view()->child_count());
    388 
    389   views::View* drag_view = target_view()->child_at(0);
    390   ASSERT_TRUE(drag_view != NULL);
    391 
    392   // Move mouse to center of menu and press button.
    393   ui_test_utils::MoveMouseToCenterAndPress(
    394       drag_view,
    395       ui_controls::LEFT,
    396       ui_controls::DOWN,
    397       CreateEventTask(this, &MenuViewDragAndDropTestNestedDrag::Step2));
    398 }
    399 
    400 void MenuViewDragAndDropTestNestedDrag::Step2() {
    401   views::View* drop_target = target_view()->child_at(1);
    402   gfx::Point loc(2, 0);
    403   views::View::ConvertPointToScreen(drop_target, &loc);
    404 
    405   // Start a drag.
    406   ui_controls::SendMouseMoveNotifyWhenDone(
    407       loc.x() + 3,
    408       loc.y(),
    409       CreateEventTask(this, &MenuViewDragAndDropTestNestedDrag::Step3));
    410 
    411   ScheduleMouseMoveInBackground(loc.x(), loc.y());
    412 }
    413 
    414 void MenuViewDragAndDropTestNestedDrag::Step3() {
    415   // The view should be dragging now.
    416   EXPECT_TRUE(target_view()->dragging());
    417 
    418   // Drop the item so that it's now the second item.
    419   views::View* drop_target = target_view()->child_at(1);
    420   gfx::Point loc(5, 0);
    421   views::View::ConvertPointToScreen(drop_target, &loc);
    422   ui_controls::SendMouseMove(loc.x(), loc.y());
    423 
    424   ui_controls::SendMouseEventsNotifyWhenDone(
    425       ui_controls::LEFT,
    426       ui_controls::UP,
    427       CreateEventTask(this, &MenuViewDragAndDropTestNestedDrag::Step4));
    428 }
    429 
    430 void MenuViewDragAndDropTestNestedDrag::Step4() {
    431   // Check our state.
    432   // The target view should have finished its drag, and should have dropped the
    433   // view. The main menu should not have done any drag, and the delegate should
    434   // have been asked if it wanted to close. Since the delegate did not want to
    435   // close, the menu should still be open.
    436   EXPECT_FALSE(target_view()->dragging());
    437   EXPECT_TRUE(target_view()->dropped());
    438   EXPECT_FALSE(performed_in_menu_drop());
    439   EXPECT_TRUE(asked_to_close());
    440   EXPECT_TRUE(menu()->GetSubmenu()->IsShowing());
    441 
    442   // Clean up.
    443   menu()->GetSubmenu()->Close();
    444 
    445   Done();
    446 }
    447 
    448 // Test that a nested drag (i.e. one via a child view, and not entirely
    449 // implemented in menu code) will consult the delegate before closing the view
    450 // after the drag.
    451 #if !defined(OS_WIN)  // http://crbug.com/401226
    452 VIEW_TEST(MenuViewDragAndDropTestNestedDrag,
    453           MAYBE(MenuViewDragAndDropNestedDrag))
    454 #endif
    455 
    456 class MenuViewDragAndDropForDropStayOpen : public MenuViewDragAndDropTest {
    457  public:
    458   MenuViewDragAndDropForDropStayOpen() {}
    459   virtual ~MenuViewDragAndDropForDropStayOpen() {}
    460 
    461  private:
    462   // MenuViewDragAndDropTest:
    463   virtual int GetMenuRunnerFlags() OVERRIDE;
    464   virtual void DoTestWithMenuOpen() OVERRIDE;
    465 };
    466 
    467 int MenuViewDragAndDropForDropStayOpen::GetMenuRunnerFlags() {
    468   return views::MenuRunner::HAS_MNEMONICS |
    469          views::MenuRunner::NESTED_DRAG |
    470          views::MenuRunner::FOR_DROP;
    471 }
    472 
    473 void MenuViewDragAndDropForDropStayOpen::DoTestWithMenuOpen() {
    474   views::SubmenuView* submenu = menu()->GetSubmenu();
    475   ASSERT_TRUE(submenu);
    476   ASSERT_TRUE(submenu->IsShowing());
    477 
    478   views::MenuController* controller = menu()->GetMenuController();
    479   ASSERT_TRUE(controller);
    480   EXPECT_FALSE(controller->IsCancelAllTimerRunningForTest());
    481 
    482   Done();
    483 }
    484 
    485 // Test that if a menu is opened for a drop which is handled by a child view
    486 // that the menu does not immediately try to close.
    487 VIEW_TEST(MenuViewDragAndDropForDropStayOpen, MenuViewStaysOpenForNestedDrag)
    488 
    489 class MenuViewDragAndDropForDropCancel : public MenuViewDragAndDropTest {
    490  public:
    491   MenuViewDragAndDropForDropCancel() {}
    492   virtual ~MenuViewDragAndDropForDropCancel() {}
    493 
    494  private:
    495   // MenuViewDragAndDropTest:
    496   virtual int GetMenuRunnerFlags() OVERRIDE;
    497   virtual void DoTestWithMenuOpen() OVERRIDE;
    498 };
    499 
    500 int MenuViewDragAndDropForDropCancel::GetMenuRunnerFlags() {
    501   return views::MenuRunner::HAS_MNEMONICS | views::MenuRunner::FOR_DROP;
    502 }
    503 
    504 void MenuViewDragAndDropForDropCancel::DoTestWithMenuOpen() {
    505   views::SubmenuView* submenu = menu()->GetSubmenu();
    506   ASSERT_TRUE(submenu);
    507   ASSERT_TRUE(submenu->IsShowing());
    508 
    509   views::MenuController* controller = menu()->GetMenuController();
    510   ASSERT_TRUE(controller);
    511   EXPECT_TRUE(controller->IsCancelAllTimerRunningForTest());
    512 
    513   Done();
    514 }
    515 
    516 // Test that if a menu is opened for a drop handled entirely by menu code, the
    517 // menu will try to close if it does not receive any drag updates.
    518 VIEW_TEST(MenuViewDragAndDropForDropCancel, MenuViewCancelsForOwnDrag)
    519