Implement double/triple click functionality in views textfield. Also changed...

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
parent f9597714
...@@ -59,7 +59,9 @@ NativeTextfieldViews::NativeTextfieldViews(Textfield* parent) ...@@ -59,7 +59,9 @@ NativeTextfieldViews::NativeTextfieldViews(Textfield* parent)
text_offset_(0), text_offset_(0),
insert_(true), insert_(true),
is_cursor_visible_(false), 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_); set_border(text_border_);
// Multiline is not supported. // Multiline is not supported.
...@@ -77,14 +79,8 @@ NativeTextfieldViews::~NativeTextfieldViews() { ...@@ -77,14 +79,8 @@ NativeTextfieldViews::~NativeTextfieldViews() {
// NativeTextfieldViews, View overrides: // NativeTextfieldViews, View overrides:
bool NativeTextfieldViews::OnMousePressed(const views::MouseEvent& e) { bool NativeTextfieldViews::OnMousePressed(const views::MouseEvent& e) {
textfield_->RequestFocus(); if (HandleMousePressed(e))
if (e.IsLeftMouseButton()) { SchedulePaint();
size_t pos = FindCursorPosition(e.location());
if (model_->MoveCursorTo(pos, false)) {
UpdateCursorBoundsAndTextOffset();
SchedulePaint();
}
}
return true; return true;
} }
...@@ -784,6 +780,47 @@ size_t NativeTextfieldViews::FindCursorPosition(const gfx::Point& point) const { ...@@ -784,6 +780,47 @@ size_t NativeTextfieldViews::FindCursorPosition(const gfx::Point& point) const {
return left_pos; 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() { void NativeTextfieldViews::PropagateTextChange() {
textfield_->SyncText(); textfield_->SyncText();
Textfield::Controller* controller = textfield_->GetController(); Textfield::Controller* controller = textfield_->GetController();
......
...@@ -14,6 +14,10 @@ ...@@ -14,6 +14,10 @@
#include "views/controls/textfield/native_textfield_wrapper.h" #include "views/controls/textfield/native_textfield_wrapper.h"
#include "views/view.h" #include "views/view.h"
namespace base {
class Time;
}
namespace gfx { namespace gfx {
class Canvas; class Canvas;
} // namespace } // namespace
...@@ -108,6 +112,13 @@ class NativeTextfieldViews : public views::View, ...@@ -108,6 +112,13 @@ class NativeTextfieldViews : public views::View,
// Enable/Disable TextfieldViews implementation for Textfield. // Enable/Disable TextfieldViews implementation for Textfield.
static void SetEnableTextfieldViews(bool enabled); static void SetEnableTextfieldViews(bool enabled);
enum ClickState {
TRACKING_DOUBLE_CLICK,
TRACKING_TRIPLE_CLICK,
NONE,
};
private: private:
friend class NativeTextfieldViewsTest; friend class NativeTextfieldViewsTest;
...@@ -162,6 +173,13 @@ class NativeTextfieldViews : public views::View, ...@@ -162,6 +173,13 @@ class NativeTextfieldViews : public views::View,
// Find a cusor position for given |point| in this views coordinates. // Find a cusor position for given |point| in this views coordinates.
size_t FindCursorPosition(const gfx::Point& point) const; 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) // Utility function to inform the parent textfield (and its controller if any)
// that the text in the textfield has changed. // that the text in the textfield has changed.
void PropagateTextChange(); void PropagateTextChange();
...@@ -193,6 +211,15 @@ class NativeTextfieldViews : public views::View, ...@@ -193,6 +211,15 @@ class NativeTextfieldViews : public views::View,
// A runnable method factory for callback to update the cursor. // A runnable method factory for callback to update the cursor.
ScopedRunnableMethodFactory<NativeTextfieldViews> cursor_timer_; 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. // Context menu and its content list for the textfield.
scoped_ptr<ui::SimpleMenuModel> context_menu_contents_; scoped_ptr<ui::SimpleMenuModel> context_menu_contents_;
scoped_ptr<Menu2> context_menu_menu_; scoped_ptr<Menu2> context_menu_menu_;
......
...@@ -104,6 +104,10 @@ class NativeTextfieldViewsTest : public ViewsTestBase, ...@@ -104,6 +104,10 @@ class NativeTextfieldViewsTest : public ViewsTestBase,
return textfield_view_->context_menu_menu_.get(); return textfield_view_->context_menu_menu_.get();
} }
NativeTextfieldViews::ClickState GetClickState() {
return textfield_view_->click_state_;
}
protected: protected:
bool SendKeyEventToTextfieldViews(ui::KeyboardCode key_code, bool SendKeyEventToTextfieldViews(ui::KeyboardCode key_code,
bool shift, bool shift,
...@@ -428,6 +432,31 @@ TEST_F(NativeTextfieldViewsTest, ContextMenuDisplayTest) { ...@@ -428,6 +432,31 @@ TEST_F(NativeTextfieldViewsTest, ContextMenuDisplayTest) {
VerifyTextfieldContextMenuContents(true, GetContextMenu()->model()); 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) { TEST_F(NativeTextfieldViewsTest, ReadOnlyTest) {
scoped_ptr<TestViewsDelegate> test_views_delegate(new TestViewsDelegate()); scoped_ptr<TestViewsDelegate> test_views_delegate(new TestViewsDelegate());
AutoReset<views::ViewsDelegate*> auto_reset( AutoReset<views::ViewsDelegate*> auto_reset(
......
...@@ -235,6 +235,41 @@ void TextfieldViewsModel::SelectAll() { ...@@ -235,6 +235,41 @@ void TextfieldViewsModel::SelectAll() {
selection_begin_ = 0; 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() { void TextfieldViewsModel::ClearSelection() {
selection_begin_ = cursor_pos_; selection_begin_ = cursor_pos_;
} }
...@@ -292,6 +327,11 @@ string16 TextfieldViewsModel::GetVisibleText(size_t begin, size_t end) const { ...@@ -292,6 +327,11 @@ string16 TextfieldViewsModel::GetVisibleText(size_t begin, size_t end) const {
return text_.substr(begin, end - begin); 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 { size_t TextfieldViewsModel::GetSafePosition(size_t position) const {
if (position > text_.length()) { if (position > text_.length()) {
return text_.length(); return text_.length();
......
...@@ -123,6 +123,9 @@ class TextfieldViewsModel { ...@@ -123,6 +123,9 @@ class TextfieldViewsModel {
// Selects all text. // Selects all text.
void SelectAll(); void SelectAll();
// Selects the word at which the cursor is currently positioned.
void SelectWord();
// Clears selection. // Clears selection.
void ClearSelection(); void ClearSelection();
...@@ -155,6 +158,9 @@ class TextfieldViewsModel { ...@@ -155,6 +158,9 @@ class TextfieldViewsModel {
// Returns the visible text given |start| and |end|. // Returns the visible text given |start| and |end|.
string16 GetVisibleText(size_t start, size_t end) const; 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 // Returns the normalized cursor position that does not exceed the
// text length. // text length.
size_t GetSafePosition(size_t position) const; size_t GetSafePosition(size_t position) const;
......
...@@ -361,6 +361,49 @@ TEST_F(TextfieldViewsModelTest, Clipboard) { ...@@ -361,6 +361,49 @@ TEST_F(TextfieldViewsModelTest, Clipboard) {
EXPECT_EQ(29U, model.cursor_pos()); 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) { TEST_F(TextfieldViewsModelTest, RangeTest) {
TextfieldViewsModel model; TextfieldViewsModel model;
model.Append(ASCIIToUTF16("HELLO WORLD")); model.Append(ASCIIToUTF16("HELLO WORLD"));
......
...@@ -10,11 +10,7 @@ namespace views { ...@@ -10,11 +10,7 @@ namespace views {
Event::Event(EventType type, int flags) Event::Event(EventType type, int flags)
: type_(type), : type_(type),
#if defined(OS_WIN) time_stamp_(base::Time::NowFromSystemTime()),
time_stamp_(GetTickCount()),
#else
time_stamp_(0),
#endif
flags_(flags) { flags_(flags) {
} }
......
...@@ -7,6 +7,7 @@ ...@@ -7,6 +7,7 @@
#pragma once #pragma once
#include "base/basictypes.h" #include "base/basictypes.h"
#include "base/time.h"
#include "gfx/point.h" #include "gfx/point.h"
#include "ui/base/keycodes/keyboard_codes.h" #include "ui/base/keycodes/keyboard_codes.h"
...@@ -80,8 +81,8 @@ class Event { ...@@ -80,8 +81,8 @@ class Event {
return type_; return type_;
} }
// Return the event time stamp in ticks // Return the event time stamp.
int GetTimeStamp() const { const base::Time& GetTimeStamp() const {
return time_stamp_; return time_stamp_;
} }
...@@ -157,7 +158,7 @@ class Event { ...@@ -157,7 +158,7 @@ class Event {
void operator=(const Event&); void operator=(const Event&);
EventType type_; EventType type_;
int time_stamp_; base::Time time_stamp_;
int flags_; int flags_;
}; };
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment