| // Copyright 2014 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #ifndef UI_VIEWS_CONTROLS_TEXTFIELD_TEXTFIELD_MODEL_H_ |
| #define UI_VIEWS_CONTROLS_TEXTFIELD_TEXTFIELD_MODEL_H_ |
| |
| #include <stddef.h> |
| |
| #include <list> |
| #include <memory> |
| #include <vector> |
| |
| #include "base/gtest_prod_util.h" |
| #include "base/macros.h" |
| #include "base/strings/string16.h" |
| #include "ui/base/ime/composition_text.h" |
| #include "ui/gfx/render_text.h" |
| #include "ui/gfx/text_constants.h" |
| #include "ui/views/views_export.h" |
| |
| namespace views { |
| |
| namespace internal { |
| // Internal Edit class that keeps track of edits for undo/redo. |
| class Edit; |
| |
| // The types of merge behavior implemented by Edit operations. |
| enum class MergeType { |
| // The edit should not usually be merged with next edit. |
| kDoNotMerge, |
| // The edit should be merged with next edit when possible. |
| kMergeable, |
| // The edit should be merged with the prior edit, even if marked kDoNotMerge. |
| kForceMerge, |
| }; |
| |
| } // namespace internal |
| |
| namespace test { |
| class BridgedNativeWidgetTest; |
| } // namespace test |
| |
| // A model that represents text content for a views::Textfield. |
| // It supports editing, selection and cursor manipulation. |
| class VIEWS_EXPORT TextfieldModel { |
| public: |
| // Delegate interface implemented by the textfield view class to provide |
| // additional functionalities required by the model. |
| class VIEWS_EXPORT Delegate { |
| public: |
| // Called when the current composition text is confirmed or cleared. |
| virtual void OnCompositionTextConfirmedOrCleared() = 0; |
| |
| // Called any time that the text property is modified in TextfieldModel |
| virtual void OnTextChanged() {} |
| |
| protected: |
| virtual ~Delegate(); |
| }; |
| |
| explicit TextfieldModel(Delegate* delegate); |
| virtual ~TextfieldModel(); |
| |
| // Edit related methods. |
| |
| const base::string16& text() const { return render_text_->text(); } |
| // Sets the text. Returns true if the text was modified. The current |
| // composition text will be confirmed first. Setting the same text, even with |
| // an updated |cursor_position|, will neither add edit history nor change the |
| // cursor because it's neither a user visible change nor user-initiated |
| // change. This allows clients to set the same text multiple times without |
| // messing up edit history. The resulting history edit will have |
| // |new_cursor_pos| set to |cursor_position|. This is important even if |
| // subsequent calls will override the cursor position because updating the |
| // cursor alone won't update the edit history. I.e. the cursor position after |
| // applying or redoing the edit will be determined by |cursor_position|. |
| bool SetText(const base::string16& new_text, size_t cursor_position); |
| |
| gfx::RenderText* render_text() { return render_text_.get(); } |
| |
| // Inserts given |new_text| at the current cursor position. |
| // The current composition text will be cleared. |
| void InsertText(const base::string16& new_text) { |
| InsertTextInternal(new_text, false); |
| } |
| |
| // Inserts a character at the current cursor position. |
| void InsertChar(base::char16 c) { |
| InsertTextInternal(base::string16(&c, 1), true); |
| } |
| |
| // Replaces characters at the current position with characters in given text. |
| // The current composition text will be cleared. |
| void ReplaceText(const base::string16& new_text) { |
| ReplaceTextInternal(new_text, false); |
| } |
| |
| // Replaces the char at the current position with given character. |
| void ReplaceChar(base::char16 c) { |
| ReplaceTextInternal(base::string16(&c, 1), true); |
| } |
| |
| // Appends the text. |
| // The current composition text will be confirmed. |
| void Append(const base::string16& new_text); |
| |
| // Deletes the first character after the current cursor position (as if, the |
| // the user has pressed delete key in the textfield). Returns true if |
| // the deletion is successful. If |add_to_kill_buffer| is true, the deleted |
| // text is copied to the kill buffer. |
| // If there is composition text, it'll be deleted instead. |
| bool Delete(bool add_to_kill_buffer = false); |
| |
| // Deletes the first character before the current cursor position (as if, the |
| // the user has pressed backspace key in the textfield). Returns true if |
| // the removal is successful. If |add_to_kill_buffer| is true, the deleted |
| // text is copied to the kill buffer. |
| // If there is composition text, it'll be deleted instead. |
| bool Backspace(bool add_to_kill_buffer = false); |
| |
| // Cursor related methods. |
| |
| // Returns the current cursor position. |
| size_t GetCursorPosition() const; |
| |
| // Moves the cursor, see RenderText for additional details. |
| // The current composition text will be confirmed. |
| void MoveCursor(gfx::BreakType break_type, |
| gfx::VisualCursorDirection direction, |
| gfx::SelectionBehavior selection_behavior); |
| |
| // Updates the cursor to the specified selection model. Any composition text |
| // will be confirmed, which may alter the specified selection range start. |
| bool MoveCursorTo(const gfx::SelectionModel& cursor); |
| |
| // Sugar for MoveCursorTo({0, CURSOR_FORWARD}). |
| bool MoveCursorTo(size_t pos); |
| |
| // Calls the corresponding function on the associated RenderText instance. Any |
| // composition text will be confirmed. |
| bool MoveCursorTo(const gfx::Point& point, bool select); |
| |
| // Selection related methods. |
| |
| // Returns the primary selected text associated with the cursor. Does not |
| // return secondary selections. |
| base::string16 GetSelectedText() const; |
| |
| // The current composition text will be confirmed. If |primary| is true, the |
| // selection starts with the range's start position and ends with the range's |
| // end position; therefore the cursor position becomes the end position. If |
| // |primary| is false, then the selection is not associated with the cursor. |
| void SelectRange(const gfx::Range& range, bool primary = true); |
| |
| // The current composition text will be confirmed. |
| // render_text_'s selection model is set to |sel|. |
| void SelectSelectionModel(const gfx::SelectionModel& sel); |
| |
| // Select the entire text range. If |reversed| is true, the range will end at |
| // the logical beginning of the text; this generally shows the leading portion |
| // of text that overflows its display area. |
| // The current composition text will be confirmed. |
| void SelectAll(bool reversed); |
| |
| // Selects the word at which the cursor is currently positioned. If there is a |
| // non-empty selection, the selection bounds are extended to their nearest |
| // word boundaries. |
| // The current composition text will be confirmed. |
| void SelectWord(); |
| |
| // Clears selection. |
| // The current composition text will be confirmed. |
| void ClearSelection(); |
| |
| // Returns true if there is an undoable edit. |
| bool CanUndo(); |
| |
| // Returns true if there is an redoable edit. |
| bool CanRedo(); |
| |
| // Undo edit. Returns true if undo changed the text. |
| bool Undo(); |
| |
| // Redo edit. Returns true if redo changed the text. |
| bool Redo(); |
| |
| // Cuts the currently selected text and puts it to clipboard. Returns true |
| // if text has changed after cutting. |
| bool Cut(); |
| |
| // Copies the currently selected text and puts it to clipboard. Returns true |
| // if something was copied to the clipboard. |
| bool Copy(); |
| |
| // Pastes text from the clipboard at current cursor position. Returns true |
| // if any text is pasted. |
| bool Paste(); |
| |
| // Transposes the characters to either side of the insertion point and |
| // advances the insertion point past both of them. Returns true if text is |
| // changed. |
| bool Transpose(); |
| |
| // Pastes text from the kill buffer at the current cursor position. Returns |
| // true if the text has changed after yanking. |
| bool Yank(); |
| |
| // Tells if any text is selected, even if the selection is in composition |
| // text. |primary_only| indicates whether secondary selections should also be |
| // considered. |
| bool HasSelection(bool primary_only = false) const; |
| |
| // Deletes the selected text. This method shouldn't be called with |
| // composition text. |
| void DeleteSelection(); |
| |
| // Deletes the selected text (if any) and insert text at given position. |
| void DeletePrimarySelectionAndInsertTextAt(const base::string16& new_text, |
| size_t position); |
| |
| // Retrieves the text content in a given range. |
| base::string16 GetTextFromRange(const gfx::Range& range) const; |
| |
| // Retrieves the range containing all text in the model. |
| void GetTextRange(gfx::Range* range) const; |
| |
| // Sets composition text and attributes. If there is composition text already, |
| // it'll be replaced by the new one. Otherwise, current selection will be |
| // replaced. If there is no selection, the composition text will be inserted |
| // at the insertion point. |
| // Any changes to the model except text insertion will confirm the current |
| // composition text. |
| void SetCompositionText(const ui::CompositionText& composition); |
| |
| #if defined(OS_CHROMEOS) |
| // Return the text range corresponding to the autocorrected text. |
| const gfx::Range& autocorrect_range() const { return autocorrect_range_; } |
| |
| // Replace the text in the specified range with the autocorrect text and |
| // store necessary metadata (The size of the new text + the original text) |
| // to be able to undo this change if needed. |
| bool SetAutocorrectRange(const base::string16& autocorrect_text, |
| const gfx::Range& range); |
| #endif |
| |
| // Puts the text in the specified range into composition mode. |
| // This method should not be called with composition text or an invalid range. |
| // The provided range is checked against the string's length, if |range| is |
| // out of bounds, the composition will be cleared. |
| void SetCompositionFromExistingText(const gfx::Range& range); |
| |
| // Converts current composition text into final content and returns the |
| // length of the text committed. |
| uint32_t ConfirmCompositionText(); |
| |
| // Removes current composition text. |
| void CancelCompositionText(); |
| |
| // Retrieves the range of current composition text. |
| void GetCompositionTextRange(gfx::Range* range) const; |
| |
| // Returns true if there is composition text. |
| bool HasCompositionText() const; |
| |
| // Clears all edit history. |
| void ClearEditHistory(); |
| |
| private: |
| friend class internal::Edit; |
| friend class test::BridgedNativeWidgetTest; |
| friend class TextfieldModelTest; |
| friend class TextfieldTest; |
| |
| FRIEND_TEST_ALL_PREFIXES(TextfieldModelTest, UndoRedo_BasicTest); |
| FRIEND_TEST_ALL_PREFIXES(TextfieldModelTest, UndoRedo_CutCopyPasteTest); |
| FRIEND_TEST_ALL_PREFIXES(TextfieldModelTest, UndoRedo_ReplaceTest); |
| |
| // Insert the given |new_text| at the cursor. |mergeable| indicates if this |
| // operation can be merged with previous edits in the history. Will delete any |
| // selected text. |
| void InsertTextInternal(const base::string16& new_text, bool mergeable); |
| |
| // Replace the current selected text with the given |new_text|. |mergeable| |
| // indicates if this operation can be merged with previous edits in the |
| // history. |
| void ReplaceTextInternal(const base::string16& new_text, bool mergeable); |
| |
| // Clears redo history. |
| void ClearRedoHistory(); |
| |
| // Executes and records edit operations. |
| void ExecuteAndRecordDelete(std::vector<gfx::Range> ranges, bool mergeable); |
| void ExecuteAndRecordReplaceSelection(internal::MergeType merge_type, |
| const base::string16& new_text); |
| void ExecuteAndRecordReplace(internal::MergeType merge_type, |
| std::vector<gfx::Range> replacement_range, |
| size_t new_cursor_pos, |
| const base::string16& new_text, |
| size_t new_text_start); |
| void ExecuteAndRecordInsert(const base::string16& new_text, bool mergeable); |
| |
| // Adds or merges |edit| into the edit history. |
| void AddOrMergeEditHistory(std::unique_ptr<internal::Edit> edit); |
| |
| // Modify the text buffer in following way: |
| // 1) Delete the |deletions|. |
| // 2) Insert the |insertion_texts| at the |insertion_positions|. |
| // 3) Select |primary_selection| and |secondary_selections|. |
| // Deletions and insertions are applied in order and affect later edit |
| // indices. E.g., given 'xyz', inserting 'A' at 1 and 'B' at 2 will result in |
| // 'xAByz', not 'xAyBz'. On the other hand, inserting 'B' at 2 then 'A' at 1 |
| // will result in 'xAyBz'. Thus, for applying or redoing edits, they should be |
| // ordered with increasing indices; while for undoing edits, they should be |
| // ordered decreasing. |
| void ModifyText(const std::vector<gfx::Range>& deletions, |
| const std::vector<base::string16>& insertion_texts, |
| const std::vector<size_t>& insertion_positions, |
| const gfx::Range& primary_selection, |
| const std::vector<gfx::Range>& secondary_selections); |
| |
| // Calls render_text->SetText() and delegate's callback. |
| void SetRenderTextText(const base::string16& text); |
| |
| void ClearComposition(); |
| |
| // Clears the kill buffer. Used to clear global state between tests. |
| static void ClearKillBuffer(); |
| |
| // The TextfieldModel::Delegate instance should be provided by the owner. |
| Delegate* delegate_; |
| |
| // The stylized text, cursor, selection, and the visual layout model. |
| std::unique_ptr<gfx::RenderText> render_text_; |
| |
| gfx::Range composition_range_; |
| |
| #if defined(OS_CHROMEOS) |
| gfx::Range autocorrect_range_; |
| // Original text is the text that was replaced by the autocorrect feature. |
| // This should be restored if the Undo button corresponding to the Autocorrect |
| // window is pressed. |
| base::string16 original_text_; |
| #endif |
| |
| // The list of Edits. The oldest Edits are at the front of the list, and the |
| // newest ones are at the back of the list. |
| using EditHistory = std::list<std::unique_ptr<internal::Edit>>; |
| EditHistory edit_history_; |
| |
| // An iterator that points to the current edit that can be undone. |
| // This iterator moves from the |end()|, meaning no edit to undo, |
| // to the last element (one before |end()|), meaning no edit to redo. |
| // |
| // There is no edit to undo (== end()) when: |
| // 1) in initial state. (nothing to undo) |
| // 2) very 1st edit is undone. |
| // 3) all edit history is removed. |
| // There is no edit to redo (== last element or no element) when: |
| // 1) in initial state. (nothing to redo) |
| // 2) new edit is added. (redo history is cleared) |
| // 3) redone all undone edits. |
| EditHistory::iterator current_edit_; |
| |
| DISALLOW_COPY_AND_ASSIGN(TextfieldModel); |
| }; |
| |
| } // namespace views |
| |
| #endif // UI_VIEWS_CONTROLS_TEXTFIELD_TEXTFIELD_MODEL_H_ |