summaryrefslogtreecommitdiffstats
path: root/views
diff options
context:
space:
mode:
authorvarunjain@chromium.org <varunjain@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2011-01-27 01:17:17 +0000
committervarunjain@chromium.org <varunjain@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2011-01-27 01:17:17 +0000
commit188da519ad3504c0138aa0033cae30e2684ff75d (patch)
tree752cd5a19f8192b0b90657da409742ba830bcbbb /views
parentf9597714f543497fdafabde1a0df9073f113840d (diff)
downloadchromium_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.cc55
-rw-r--r--views/controls/textfield/native_textfield_views.h27
-rw-r--r--views/controls/textfield/native_textfield_views_unittest.cc29
-rw-r--r--views/controls/textfield/textfield_views_model.cc40
-rw-r--r--views/controls/textfield/textfield_views_model.h6
-rw-r--r--views/controls/textfield/textfield_views_model_unittest.cc43
-rw-r--r--views/event.cc6
-rw-r--r--views/event.h7
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_;
};