diff options
author | varunjain@chromium.org <varunjain@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2011-01-27 01:17:17 +0000 |
---|---|---|
committer | varunjain@chromium.org <varunjain@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2011-01-27 01:17:17 +0000 |
commit | 188da519ad3504c0138aa0033cae30e2684ff75d (patch) | |
tree | 752cd5a19f8192b0b90657da409742ba830bcbbb /views | |
parent | f9597714f543497fdafabde1a0df9073f113840d (diff) | |
download | chromium_src-188da519ad3504c0138aa0033cae30e2684ff75d.zip chromium_src-188da519ad3504c0138aa0033cae30e2684ff75d.tar.gz chromium_src-188da519ad3504c0138aa0033cae30e2684ff75d.tar.bz2 |
Implement double/triple click functionality in views textfield. Also changed views::Event::time_stamp_ to a platform independent time stamp.
@beng: please review views::Event changes (event.cc,event.h)
@oshima: please review the rest
BUG=none
TEST=new tests added.
Review URL: http://codereview.chromium.org/6267002
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@72736 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'views')
-rw-r--r-- | views/controls/textfield/native_textfield_views.cc | 55 | ||||
-rw-r--r-- | views/controls/textfield/native_textfield_views.h | 27 | ||||
-rw-r--r-- | views/controls/textfield/native_textfield_views_unittest.cc | 29 | ||||
-rw-r--r-- | views/controls/textfield/textfield_views_model.cc | 40 | ||||
-rw-r--r-- | views/controls/textfield/textfield_views_model.h | 6 | ||||
-rw-r--r-- | views/controls/textfield/textfield_views_model_unittest.cc | 43 | ||||
-rw-r--r-- | views/event.cc | 6 | ||||
-rw-r--r-- | views/event.h | 7 |
8 files changed, 196 insertions, 17 deletions
diff --git a/views/controls/textfield/native_textfield_views.cc b/views/controls/textfield/native_textfield_views.cc index a4324be2..08f93e3 100644 --- a/views/controls/textfield/native_textfield_views.cc +++ b/views/controls/textfield/native_textfield_views.cc @@ -59,7 +59,9 @@ NativeTextfieldViews::NativeTextfieldViews(Textfield* parent) text_offset_(0), insert_(true), is_cursor_visible_(false), - ALLOW_THIS_IN_INITIALIZER_LIST(cursor_timer_(this)) { + ALLOW_THIS_IN_INITIALIZER_LIST(cursor_timer_(this)), + last_mouse_press_time_(base::Time::FromInternalValue(0)), + click_state_(NONE) { set_border(text_border_); // Multiline is not supported. @@ -77,14 +79,8 @@ NativeTextfieldViews::~NativeTextfieldViews() { // NativeTextfieldViews, View overrides: bool NativeTextfieldViews::OnMousePressed(const views::MouseEvent& e) { - textfield_->RequestFocus(); - if (e.IsLeftMouseButton()) { - size_t pos = FindCursorPosition(e.location()); - if (model_->MoveCursorTo(pos, false)) { - UpdateCursorBoundsAndTextOffset(); - SchedulePaint(); - } - } + if (HandleMousePressed(e)) + SchedulePaint(); return true; } @@ -784,6 +780,47 @@ size_t NativeTextfieldViews::FindCursorPosition(const gfx::Point& point) const { return left_pos; } +bool NativeTextfieldViews::HandleMousePressed(const views::MouseEvent& e) { + textfield_->RequestFocus(); + base::TimeDelta time_delta = e.GetTimeStamp() - last_mouse_press_time_; + gfx::Point location_delta = e.location().Subtract(last_mouse_press_location_); + last_mouse_press_time_ = e.GetTimeStamp(); + last_mouse_press_location_ = e.location(); + if (e.IsLeftMouseButton()) { + if (!ExceededDragThreshold(location_delta.x(), location_delta.y()) + && time_delta.InMilliseconds() <= GetDoubleClickTimeMS()) { + // Multiple mouse press detected. Check for double or triple. + switch (click_state_) { + case TRACKING_DOUBLE_CLICK: + click_state_ = TRACKING_TRIPLE_CLICK; + model_->SelectWord(); + return true; + case TRACKING_TRIPLE_CLICK: + click_state_ = NONE; + model_->SelectAll(); + return true; + case NONE: + click_state_ = TRACKING_DOUBLE_CLICK; + SetCursorForMouseClick(e); + return true; + } + } else { + // Single mouse press. + click_state_ = TRACKING_DOUBLE_CLICK; + SetCursorForMouseClick(e); + return true; + } + } + return false; +} + +void NativeTextfieldViews::SetCursorForMouseClick(const views::MouseEvent& e) { + size_t pos = FindCursorPosition(e.location()); + if (model_->MoveCursorTo(pos, false)) { + UpdateCursorBoundsAndTextOffset(); + } +} + void NativeTextfieldViews::PropagateTextChange() { textfield_->SyncText(); Textfield::Controller* controller = textfield_->GetController(); diff --git a/views/controls/textfield/native_textfield_views.h b/views/controls/textfield/native_textfield_views.h index d474a72..fb567cf 100644 --- a/views/controls/textfield/native_textfield_views.h +++ b/views/controls/textfield/native_textfield_views.h @@ -14,6 +14,10 @@ #include "views/controls/textfield/native_textfield_wrapper.h" #include "views/view.h" +namespace base { +class Time; +} + namespace gfx { class Canvas; } // namespace @@ -108,6 +112,13 @@ class NativeTextfieldViews : public views::View, // Enable/Disable TextfieldViews implementation for Textfield. static void SetEnableTextfieldViews(bool enabled); + enum ClickState { + TRACKING_DOUBLE_CLICK, + TRACKING_TRIPLE_CLICK, + NONE, + }; + + private: friend class NativeTextfieldViewsTest; @@ -162,6 +173,13 @@ class NativeTextfieldViews : public views::View, // Find a cusor position for given |point| in this views coordinates. size_t FindCursorPosition(const gfx::Point& point) const; + // Mouse event handler. Returns true if textfield needs to be repainted. + bool HandleMousePressed(const views::MouseEvent& e); + + // Helper function that sets the cursor position at the location of mouse + // event. + void SetCursorForMouseClick(const views::MouseEvent& e); + // Utility function to inform the parent textfield (and its controller if any) // that the text in the textfield has changed. void PropagateTextChange(); @@ -193,6 +211,15 @@ class NativeTextfieldViews : public views::View, // A runnable method factory for callback to update the cursor. ScopedRunnableMethodFactory<NativeTextfieldViews> cursor_timer_; + // Time of last LEFT mouse press. Used for tracking double/triple click. + base::Time last_mouse_press_time_; + + // Position of last LEFT mouse press. Used for tracking double/triple click. + gfx::Point last_mouse_press_location_; + + // State variable to track double and triple clicks. + ClickState click_state_; + // Context menu and its content list for the textfield. scoped_ptr<ui::SimpleMenuModel> context_menu_contents_; scoped_ptr<Menu2> context_menu_menu_; diff --git a/views/controls/textfield/native_textfield_views_unittest.cc b/views/controls/textfield/native_textfield_views_unittest.cc index d8bffbe..3824868 100644 --- a/views/controls/textfield/native_textfield_views_unittest.cc +++ b/views/controls/textfield/native_textfield_views_unittest.cc @@ -104,6 +104,10 @@ class NativeTextfieldViewsTest : public ViewsTestBase, return textfield_view_->context_menu_menu_.get(); } + NativeTextfieldViews::ClickState GetClickState() { + return textfield_view_->click_state_; + } + protected: bool SendKeyEventToTextfieldViews(ui::KeyboardCode key_code, bool shift, @@ -428,6 +432,31 @@ TEST_F(NativeTextfieldViewsTest, ContextMenuDisplayTest) { VerifyTextfieldContextMenuContents(true, GetContextMenu()->model()); } +TEST_F(NativeTextfieldViewsTest, DoubleAndTripleClickTest) { + InitTextfield(Textfield::STYLE_DEFAULT); + textfield_->SetText(ASCIIToUTF16("hello world")); + MouseEvent me(MouseEvent::ET_MOUSE_PRESSED, 0, 0, Event::EF_LEFT_BUTTON_DOWN); + EXPECT_EQ(NativeTextfieldViews::NONE, GetClickState()); + + // Test for double click. + textfield_view_->OnMousePressed(me); + EXPECT_STR_EQ("", textfield_->GetSelectedText()); + EXPECT_EQ(NativeTextfieldViews::TRACKING_DOUBLE_CLICK, GetClickState()); + textfield_view_->OnMousePressed(me); + EXPECT_STR_EQ("hello", textfield_->GetSelectedText()); + EXPECT_EQ(NativeTextfieldViews::TRACKING_TRIPLE_CLICK, GetClickState()); + + // Test for triple click. + textfield_view_->OnMousePressed(me); + EXPECT_STR_EQ("hello world", textfield_->GetSelectedText()); + EXPECT_EQ(NativeTextfieldViews::NONE, GetClickState()); + + // Another click should reset back to single click. + textfield_view_->OnMousePressed(me); + EXPECT_STR_EQ("", textfield_->GetSelectedText()); + EXPECT_EQ(NativeTextfieldViews::TRACKING_DOUBLE_CLICK, GetClickState()); +} + TEST_F(NativeTextfieldViewsTest, ReadOnlyTest) { scoped_ptr<TestViewsDelegate> test_views_delegate(new TestViewsDelegate()); AutoReset<views::ViewsDelegate*> auto_reset( diff --git a/views/controls/textfield/textfield_views_model.cc b/views/controls/textfield/textfield_views_model.cc index 585840d..d9449d3 100644 --- a/views/controls/textfield/textfield_views_model.cc +++ b/views/controls/textfield/textfield_views_model.cc @@ -235,6 +235,41 @@ void TextfieldViewsModel::SelectAll() { selection_begin_ = 0; } +void TextfieldViewsModel::SelectWord() { + // First we setup selection_begin_ and cursor_pos_. There are so many cases + // because we try to emulate what select-word looks like in a gtk textfield. + // See associated testcase for different cases. + if (cursor_pos_ > 0 && cursor_pos_ < text_.length()) { + if (isalnum(text_[cursor_pos_])) { + selection_begin_ = cursor_pos_; + cursor_pos_++; + } else + selection_begin_ = cursor_pos_ - 1; + } else if (cursor_pos_ == 0) { + selection_begin_ = cursor_pos_; + if (text_.length() > 0) + cursor_pos_++; + } else { + selection_begin_ = cursor_pos_ - 1; + } + + // Now we move selection_begin_ to beginning of selection. Selection boundary + // is defined as the position where we have alpha-num character on one side + // and non-alpha-num char on the other side. + for (; selection_begin_ > 0; selection_begin_--) { + if (IsPositionAtWordSelectionBoundary(selection_begin_)) + break; + } + + // Now we move cursor_pos_ to end of selection. Selection boundary + // is defined as the position where we have alpha-num character on one side + // and non-alpha-num char on the other side. + for (; cursor_pos_ < text_.length(); cursor_pos_++) { + if (IsPositionAtWordSelectionBoundary(cursor_pos_)) + break; + } +} + void TextfieldViewsModel::ClearSelection() { selection_begin_ = cursor_pos_; } @@ -292,6 +327,11 @@ string16 TextfieldViewsModel::GetVisibleText(size_t begin, size_t end) const { return text_.substr(begin, end - begin); } +bool TextfieldViewsModel::IsPositionAtWordSelectionBoundary(size_t pos) { + return (isalnum(text_[pos - 1]) && !isalnum(text_[pos])) || + (!isalnum(text_[pos - 1]) && isalnum(text_[pos])); +} + size_t TextfieldViewsModel::GetSafePosition(size_t position) const { if (position > text_.length()) { return text_.length(); diff --git a/views/controls/textfield/textfield_views_model.h b/views/controls/textfield/textfield_views_model.h index a87157a..bcdc6bc 100644 --- a/views/controls/textfield/textfield_views_model.h +++ b/views/controls/textfield/textfield_views_model.h @@ -123,6 +123,9 @@ class TextfieldViewsModel { // Selects all text. void SelectAll(); + // Selects the word at which the cursor is currently positioned. + void SelectWord(); + // Clears selection. void ClearSelection(); @@ -155,6 +158,9 @@ class TextfieldViewsModel { // Returns the visible text given |start| and |end|. string16 GetVisibleText(size_t start, size_t end) const; + // Utility for SelectWord(). Checks whether position pos is at word boundary. + bool IsPositionAtWordSelectionBoundary(size_t pos); + // Returns the normalized cursor position that does not exceed the // text length. size_t GetSafePosition(size_t position) const; diff --git a/views/controls/textfield/textfield_views_model_unittest.cc b/views/controls/textfield/textfield_views_model_unittest.cc index 1c45d74..b0a29c4 100644 --- a/views/controls/textfield/textfield_views_model_unittest.cc +++ b/views/controls/textfield/textfield_views_model_unittest.cc @@ -361,6 +361,49 @@ TEST_F(TextfieldViewsModelTest, Clipboard) { EXPECT_EQ(29U, model.cursor_pos()); } +void SelectWordTestVerifier(TextfieldViewsModel &model, + const std::string &expected_selected_string, size_t expected_cursor_pos) { + EXPECT_STR_EQ(expected_selected_string, model.GetSelectedText()); + EXPECT_EQ(expected_cursor_pos, model.cursor_pos()); +} + +TEST_F(TextfieldViewsModelTest, SelectWordTest) { + TextfieldViewsModel model; + model.Append(ASCIIToUTF16(" HELLO !! WO RLD ")); + + // Test when cursor is at the beginning. + model.MoveCursorToStart(false); + model.SelectWord(); + SelectWordTestVerifier(model, " ", 2U); + + // Test when cursor is at the beginning of a word. + model.MoveCursorTo(2U, false); + model.SelectWord(); + SelectWordTestVerifier(model, "HELLO", 7U); + + // Test when cursor is at the end of a word. + model.MoveCursorTo(15U, false); + model.SelectWord(); + SelectWordTestVerifier(model, "WO", 15U); + + // Test when cursor is somewhere in a non-alph-numeric fragment. + for (size_t cursor_pos = 8; cursor_pos < 13U; cursor_pos++) { + model.MoveCursorTo(cursor_pos, false); + model.SelectWord(); + SelectWordTestVerifier(model, " !! ", 13U); + } + + // Test when cursor is somewhere in a whitespace fragment. + model.MoveCursorTo(17U, false); + model.SelectWord(); + SelectWordTestVerifier(model, " ", 20U); + + // Test when cursor is at the end. + model.MoveCursorToEnd(false); + model.SelectWord(); + SelectWordTestVerifier(model, " ", 24U); +} + TEST_F(TextfieldViewsModelTest, RangeTest) { TextfieldViewsModel model; model.Append(ASCIIToUTF16("HELLO WORLD")); diff --git a/views/event.cc b/views/event.cc index 7584a4d..cead011 100644 --- a/views/event.cc +++ b/views/event.cc @@ -10,11 +10,7 @@ namespace views { Event::Event(EventType type, int flags) : type_(type), -#if defined(OS_WIN) - time_stamp_(GetTickCount()), -#else - time_stamp_(0), -#endif + time_stamp_(base::Time::NowFromSystemTime()), flags_(flags) { } diff --git a/views/event.h b/views/event.h index 3815886..c893eeaf 100644 --- a/views/event.h +++ b/views/event.h @@ -7,6 +7,7 @@ #pragma once #include "base/basictypes.h" +#include "base/time.h" #include "gfx/point.h" #include "ui/base/keycodes/keyboard_codes.h" @@ -80,8 +81,8 @@ class Event { return type_; } - // Return the event time stamp in ticks - int GetTimeStamp() const { + // Return the event time stamp. + const base::Time& GetTimeStamp() const { return time_stamp_; } @@ -157,7 +158,7 @@ class Event { void operator=(const Event&); EventType type_; - int time_stamp_; + base::Time time_stamp_; int flags_; }; |