omnibox_edit_model.h revision 90dce4d38c5ff5333bea97d859d4e484e27edf0c
1// Copyright 2012 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#ifndef CHROME_BROWSER_UI_OMNIBOX_OMNIBOX_EDIT_MODEL_H_
6#define CHROME_BROWSER_UI_OMNIBOX_OMNIBOX_EDIT_MODEL_H_
7
8#include "base/basictypes.h"
9#include "base/compiler_specific.h"
10#include "base/memory/scoped_ptr.h"
11#include "base/string16.h"
12#include "base/time.h"
13#include "chrome/browser/autocomplete/autocomplete_controller_delegate.h"
14#include "chrome/browser/autocomplete/autocomplete_match.h"
15#include "chrome/browser/ui/omnibox/omnibox_controller.h"
16#include "chrome/common/metrics/proto/omnibox_event.pb.h"
17#include "chrome/common/omnibox_focus_state.h"
18#include "content/public/common/page_transition_types.h"
19#include "googleurl/src/gurl.h"
20#include "ui/base/window_open_disposition.h"
21#include "ui/gfx/native_widget_types.h"
22
23class AutocompleteController;
24class AutocompleteResult;
25struct InstantSuggestion;
26class OmniboxCurrentPageDelegate;
27class OmniboxEditController;
28class OmniboxPopupModel;
29class OmniboxView;
30class Profile;
31
32namespace gfx {
33class Image;
34class Rect;
35}
36
37// Reasons why the Omnibox focus state could change.
38enum OmniboxFocusChangeReason {
39  // Includes any explicit changes to focus. (e.g. user clicking to change
40  // focus, user tabbing to change focus, any explicit calls to SetFocus,
41  // etc.)
42  OMNIBOX_FOCUS_CHANGE_EXPLICIT,
43
44  // Focus changed to restore state from a tab the user switched to.
45  OMNIBOX_FOCUS_CHANGE_TAB_SWITCH,
46
47  // Focus changed because user started typing. This only happens when focus
48  // state is INVISIBLE (and this results in a change to VISIBLE).
49  OMNIBOX_FOCUS_CHANGE_TYPING,
50};
51
52// Reasons why the Omnibox could change into keyword mode.
53// These numeric values are used in UMA logs; do not change them.
54enum EnteredKeywordModeMethod {
55  ENTERED_KEYWORD_MODE_VIA_TAB = 0,
56  ENTERED_KEYWORD_MODE_VIA_SPACE_AT_END = 1,
57  ENTERED_KEYWORD_MODE_VIA_SPACE_IN_MIDDLE = 2,
58  ENTERED_KEYWORD_MODE_NUM_ITEMS
59};
60
61class OmniboxEditModel {
62 public:
63  struct State {
64    State(bool user_input_in_progress,
65          const string16& user_text,
66          const string16& instant_suggestion,
67          const string16& keyword,
68          bool is_keyword_hint,
69          OmniboxFocusState focus_state);
70    ~State();
71
72    bool user_input_in_progress;
73    const string16 user_text;
74    const string16 instant_suggestion;
75    const string16 keyword;
76    const bool is_keyword_hint;
77    OmniboxFocusState focus_state;
78  };
79
80  OmniboxEditModel(OmniboxView* view,
81                   OmniboxEditController* controller,
82                   Profile* profile);
83  virtual ~OmniboxEditModel();
84
85  // TODO(beaudoin): Remove this accessor when the AutocompleteController has
86  //     completely moved to OmniboxController.
87  AutocompleteController* autocomplete_controller() const {
88    return omnibox_controller_->autocomplete_controller();
89  }
90
91  void set_popup_model(OmniboxPopupModel* popup_model) {
92    omnibox_controller_->set_popup_model(popup_model);
93   }
94
95  // TODO: The edit and popup should be siblings owned by the LocationBarView,
96  // making this accessor unnecessary.
97  OmniboxPopupModel* popup_model() const {
98    return omnibox_controller_->popup_model();
99  }
100
101  OmniboxEditController* controller() const { return controller_; }
102
103  Profile* profile() const { return profile_; }
104
105  // Returns the current state.  This assumes we are switching tabs, and changes
106  // the internal state appropriately.
107  const State GetStateForTabSwitch();
108
109  // Restores local state from the saved |state|.
110  void RestoreState(const State& state);
111
112  // Returns the match for the current text. If the user has not edited the text
113  // this is the match corresponding to the permanent text.
114  AutocompleteMatch CurrentMatch();
115
116  // Called when the user wants to export the entire current text as a URL.
117  // Sets the url, and if known, the title and favicon.
118  void GetDataForURLExport(GURL* url, string16* title, gfx::Image* favicon);
119
120  // Returns true if a verbatim query should be used for Instant. A verbatim
121  // query is forced in certain situations, such as pressing delete at the end
122  // of the edit.
123  bool UseVerbatimInstant();
124
125  // Returns true if the current edit contents will be treated as a
126  // URL/navigation, as opposed to a search.
127  bool CurrentTextIsURL() const;
128
129  // Returns the match type for the current edit contents.
130  AutocompleteMatch::Type CurrentTextType() const;
131
132  // Invoked to adjust the text before writting to the clipboard for a copy
133  // (e.g. by adding 'http' to the front). |sel_min| gives the minimum position
134  // of the selection e.g. min(selection_start, selection_end). |text| is the
135  // currently selected text. If |is_all_selected| is true all the text in the
136  // edit is selected. If the url should be copied to the clipboard |write_url|
137  // is set to true and |url| set to the url to write.
138  void AdjustTextForCopy(int sel_min,
139                         bool is_all_selected,
140                         string16* text,
141                         GURL* url,
142                         bool* write_url);
143
144  bool user_input_in_progress() const { return user_input_in_progress_; }
145
146  // Sets the state of user_input_in_progress_, and notifies the observer if
147  // that state has changed.
148  void SetInputInProgress(bool in_progress);
149
150  // Updates permanent_text_ to |new_permanent_text|.  Returns true if this
151  // change should be immediately user-visible, because either the user is not
152  // editing or the edit does not have focus.
153  bool UpdatePermanentText(const string16& new_permanent_text);
154
155  // Returns the URL corresponding to the permanent text.
156  GURL PermanentURL();
157
158  // Sets the user_text_ to |text|.  Only the View should call this.
159  void SetUserText(const string16& text);
160
161  // Calls through to SearchProvider::FinalizeInstantQuery.
162  // If |skip_inline_autocomplete| is true then the |suggestion| text will be
163  // turned into final text instead of inline autocomplete suggest.
164  void FinalizeInstantQuery(const string16& input_text,
165                            const InstantSuggestion& suggestion,
166                            bool skip_inline_autocomplete);
167
168  // Sets the suggestion text.
169  void SetInstantSuggestion(const InstantSuggestion& suggestion);
170
171  // Commits the suggested text. If |skip_inline_autocomplete| is true then the
172  // suggested text will be committed as final text as if it's inputted by the
173  // user, rather than as inline autocomplete suggest.
174  // Returns true if the text was committed.
175  // TODO: can the return type be void?
176  bool CommitSuggestedText(bool skip_inline_autocomplete);
177
178  // Invoked any time the text may have changed in the edit. Updates Instant and
179  // notifies the controller.
180  void OnChanged();
181
182  // Reverts the edit model back to its unedited state (permanent text showing,
183  // no user input in progress).
184  void Revert();
185
186  // Directs the popup to start autocomplete.
187  void StartAutocomplete(bool has_selected_text,
188                         bool prevent_inline_autocomplete) const;
189
190  // Closes the popup and cancels any pending asynchronous queries.
191  void StopAutocomplete();
192
193  // Determines whether the user can "paste and go", given the specified text.
194  bool CanPasteAndGo(const string16& text) const;
195
196  // Navigates to the destination last supplied to CanPasteAndGo.
197  void PasteAndGo(const string16& text);
198
199  // Returns true if this is a paste-and-search rather than paste-and-go (or
200  // nothing).
201  bool IsPasteAndSearch(const string16& text) const;
202
203  // Asks the browser to load the popup's currently selected item, using the
204  // supplied disposition.  This may close the popup. If |for_drop| is true,
205  // it indicates the input is being accepted as part of a drop operation and
206  // the transition should be treated as LINK (so that it won't trigger the
207  // URL to be autocompleted).
208  void AcceptInput(WindowOpenDisposition disposition,
209                   bool for_drop);
210
211  // Asks the browser to load the item at |index|, with the given properties.
212  void OpenMatch(const AutocompleteMatch& match,
213                 WindowOpenDisposition disposition,
214                 const GURL& alternate_nav_url,
215                 size_t index);
216
217  OmniboxFocusState focus_state() const { return focus_state_; }
218  bool has_focus() const { return focus_state_ != OMNIBOX_FOCUS_NONE; }
219  bool is_caret_visible() const {
220    return focus_state_ == OMNIBOX_FOCUS_VISIBLE;
221  }
222
223  // Accessors for keyword-related state (see comments on keyword_ and
224  // is_keyword_hint_).
225  const string16& keyword() const { return keyword_; }
226  bool is_keyword_hint() const { return is_keyword_hint_; }
227
228  // Accepts the current keyword hint as a keyword. It always returns true for
229  // caller convenience. |entered_method| indicates how the use entered
230  // keyword mode. This parameter is only used for metrics/logging; it's not
231  // used to change user-visible behavior.
232  bool AcceptKeyword(EnteredKeywordModeMethod entered_method);
233
234  // Accepts the current temporary text as the user text.
235  void AcceptTemporaryTextAsUserText();
236
237  // Clears the current keyword.  |visible_text| is the (non-keyword) text
238  // currently visible in the edit.
239  void ClearKeyword(const string16& visible_text);
240
241  // Returns the current autocomplete result.  This logic should in the future
242  // live in AutocompleteController but resides here for now.  This method is
243  // used by AutomationProvider::AutocompleteEditGetMatches.
244  const AutocompleteResult& result() const {
245    return omnibox_controller_->result();
246  }
247
248  // Called when the view is gaining focus.  |control_down| is whether the
249  // control key is down (at the time we're gaining focus).
250  void OnSetFocus(bool control_down);
251
252  // Sets the visibility of the caret in the omnibox, if it has focus. The
253  // visibility of the caret is reset to visible if either
254  //   - The user starts typing, or
255  //   - We explicitly focus the omnibox again.
256  // The latter case must be handled in three separate places--OnSetFocus(),
257  // OmniboxView::SetFocus(), and the mouse handlers in OmniboxView. See
258  // accompanying comments for why each of these is necessary.
259  //
260  // Caret visibility is tracked per-tab and updates automatically upon
261  // switching tabs.
262  void SetCaretVisibility(bool visible);
263
264  // Sent before |OnKillFocus| and before the popup is closed.
265  void OnWillKillFocus(gfx::NativeView view_gaining_focus);
266
267  // Called when the view is losing focus.  Resets some state.
268  void OnKillFocus();
269
270  // Called when the user presses the escape key.  Decides what, if anything, to
271  // revert about any current edits.  Returns whether the key was handled.
272  bool OnEscapeKeyPressed();
273
274  // Called when the user presses or releases the control key.  Changes state as
275  // necessary.
276  void OnControlKeyChanged(bool pressed);
277
278  // Called when the user pastes in text.
279  void on_paste() { paste_state_ = PASTING; }
280
281  // Returns true if pasting is in progress.
282  bool is_pasting() const { return paste_state_ == PASTING; }
283
284  // TODO(beaudoin): Try not to expose this.
285  bool in_revert() const { return in_revert_; }
286
287  // Called when the user presses up or down.  |count| is a repeat count,
288  // negative for moving up, positive for moving down.
289  virtual void OnUpOrDownKeyPressed(int count);
290
291  // Called when any relevant data changes.  This rolls together several
292  // separate pieces of data into one call so we can update all the UI
293  // efficiently:
294  //   |text| is either the new temporary text from the user manually selecting
295  //     a different match, or the inline autocomplete text.  We distinguish by
296  //     checking if |destination_for_temporary_text_change| is NULL.
297  //   |destination_for_temporary_text_change| is NULL (if temporary text should
298  //     not change) or the pre-change destination URL (if temporary text should
299  //     change) so we can save it off to restore later.
300  //   |keyword| is the keyword to show a hint for if |is_keyword_hint| is true,
301  //     or the currently selected keyword if |is_keyword_hint| is false (see
302  //     comments on keyword_ and is_keyword_hint_).
303  void OnPopupDataChanged(
304      const string16& text,
305      GURL* destination_for_temporary_text_change,
306      const string16& keyword,
307      bool is_keyword_hint);
308
309  // Called by the OmniboxView after something changes, with details about what
310  // state changes occured.  Updates internal state, updates the popup if
311  // necessary, and returns true if any significant changes occurred.  Note that
312  // |text_differs| may be set even if |old_text| == |new_text|, e.g. if we've
313  // just committed an IME composition.
314  //
315  // If |allow_keyword_ui_change| is false then the change should not affect
316  // keyword ui state, even if the text matches a keyword exactly. This value
317  // may be false when the user is composing a text with an IME.
318  bool OnAfterPossibleChange(const string16& old_text,
319                             const string16& new_text,
320                             size_t selection_start,
321                             size_t selection_end,
322                             bool selection_differs,
323                             bool text_differs,
324                             bool just_deleted_text,
325                             bool allow_keyword_ui_change);
326
327  // TODO(beaudoin): Mac code still calls this here. We should try to untangle
328  // this.
329  // Invoked when the popup has changed its bounds to |bounds|. |bounds| here
330  // is in screen coordinates.
331  void OnPopupBoundsChanged(const gfx::Rect& bounds) {
332    omnibox_controller_->OnPopupBoundsChanged(bounds);
333  }
334
335  // Called when the results have changed in the OmniboxController.
336  void OnResultChanged(bool default_match_changed);
337
338 private:
339  friend class InstantTestBase;
340  friend class OmniboxControllerTest;
341
342  enum PasteState {
343    NONE,           // Most recent edit was not a paste.
344    PASTING,        // In the middle of doing a paste. We need this intermediate
345                    // state because OnPaste() does the actual detection of
346                    // paste, but OnAfterPossibleChange() has to update the
347                    // paste state for every edit. If OnPaste() set the state
348                    // directly to PASTED, OnAfterPossibleChange() wouldn't know
349                    // whether that represented the current edit or a past one.
350    PASTED,         // Most recent edit was a paste.
351  };
352
353  enum ControlKeyState {
354    UP,                   // The control key is not depressed.
355    DOWN_WITHOUT_CHANGE,  // The control key is depressed, and the edit's
356                          // contents/selection have not changed since it was
357                          // depressed.  This is the only state in which we
358                          // do the "ctrl-enter" behavior when the user hits
359                          // enter.
360    DOWN_WITH_CHANGE,     // The control key is depressed, and the edit's
361                          // contents/selection have changed since it was
362                          // depressed.  If the user now hits enter, we assume
363                          // he simply hasn't released the key, rather than that
364                          // he intended to hit "ctrl-enter".
365  };
366
367  // Returns true if a query to an autocomplete provider is currently
368  // in progress.  This logic should in the future live in
369  // AutocompleteController but resides here for now.  This method is used by
370  // AutomationProvider::AutocompleteEditIsQueryInProgress.
371  bool query_in_progress() const;
372
373  // Called whenever user_text_ should change.
374  void InternalSetUserText(const string16& text);
375
376  // Returns true if a keyword is selected.
377  bool KeywordIsSelected() const;
378
379  // Conversion between user text and display text. User text is the text the
380  // user has input. Display text is the text being shown in the edit. The
381  // two are different if a keyword is selected.
382  string16 DisplayTextFromUserText(const string16& text) const;
383  string16 UserTextFromDisplayText(const string16& text) const;
384
385  // If there's a selected match, copies it into |match|. Else, returns the
386  // default match for the current text, as well as the alternate nav URL, if
387  // |alternate_nav_url| is non-NULL and there is such a URL.
388  void GetInfoForCurrentText(AutocompleteMatch* match,
389                             GURL* alternate_nav_url) const;
390
391  // Reverts the edit box from a temporary text back to the original user text.
392  // If |revert_popup| is true then the popup will be reverted as well.
393  void RevertTemporaryText(bool revert_popup);
394
395  // Accepts current keyword if the user just typed a space at the end of
396  // |new_text|.  This handles both of the following cases:
397  //   (assume "foo" is a keyword, | is the input caret, [] is selected text)
398  //   foo| -> foo |      (a space was appended to a keyword)
399  //   foo[bar] -> foo |  (a space replaced other text after a keyword)
400  // Returns true if the current keyword is accepted.
401  bool MaybeAcceptKeywordBySpace(const string16& new_text);
402
403  // Checks whether the user inserted a space into |old_text| and by doing so
404  // created a |new_text| that looks like "<keyword> <search phrase>".
405  bool CreatedKeywordSearchByInsertingSpaceInMiddle(
406      const string16& old_text,
407      const string16& new_text,
408      size_t caret_position) const;
409
410  // Tries to start an Instant preview for |match|. Returns true if Instant
411  // processed the match.
412  bool DoInstant(const AutocompleteMatch& match);
413
414  // Starts a DNS prefetch for the given |match|.
415  void DoPreconnect(const AutocompleteMatch& match);
416
417  // Checks if a given character is a valid space character for accepting
418  // keyword.
419  static bool IsSpaceCharForAcceptingKeyword(wchar_t c);
420
421  // Classify the current page being viewed as, for example, the new tab
422  // page or a normal web page.  Used for logging omnibox events for
423  // UMA opted-in users.  Examines the user's profile to determine if the
424  // current page is the user's home page.
425  metrics::OmniboxEventProto::PageClassification ClassifyPage(
426      const GURL& gurl) const;
427
428  // Sets |match| and |alternate_nav_url| based on classifying |text|.
429  // |alternate_nav_url| may be NULL.
430  void ClassifyStringForPasteAndGo(const string16& text,
431                                   AutocompleteMatch* match,
432                                   GURL* alternate_nav_url) const;
433
434  // If focus_state_ does not match |state|, we update it and notify the
435  // InstantController about the change (passing along the |reason| for the
436  // change). If the caret visibility changes, we call ApplyCaretVisibility() on
437  // the view.
438  void SetFocusState(OmniboxFocusState state, OmniboxFocusChangeReason reason);
439
440  scoped_ptr<OmniboxController> omnibox_controller_;
441
442  OmniboxView* view_;
443
444  OmniboxEditController* controller_;
445
446  scoped_ptr<OmniboxCurrentPageDelegate> delegate_;
447
448  OmniboxFocusState focus_state_;
449
450  // The URL of the currently displayed page.
451  string16 permanent_text_;
452
453  // This flag is true when the user has modified the contents of the edit, but
454  // not yet accepted them.  We use this to determine when we need to save
455  // state (on switching tabs) and whether changes to the page URL should be
456  // immediately displayed.
457  // This flag will be true in a superset of the cases where the popup is open.
458  bool user_input_in_progress_;
459
460  // The text that the user has entered.  This does not include inline
461  // autocomplete text that has not yet been accepted.
462  string16 user_text_;
463
464  // We keep track of when the user began modifying the omnibox text.
465  // This should be valid whenever user_input_in_progress_ is true.
466  base::TimeTicks time_user_first_modified_omnibox_;
467
468  // When the user closes the popup, we need to remember the URL for their
469  // desired choice, so that if they hit enter without reopening the popup we
470  // know where to go.  We could simply rerun autocomplete in this case, but
471  // we'd need to either wait for all results to come in (unacceptably slow) or
472  // do the wrong thing when the user had chosen some provider whose results
473  // were not returned instantaneously.
474  //
475  // This variable is only valid when user_input_in_progress_ is true, since
476  // when it is false the user has either never input anything (so there won't
477  // be a value here anyway) or has canceled their input, which should be
478  // treated the same way.  Also, since this is for preserving a desired URL
479  // after the popup has been closed, we ignore this if the popup is open, and
480  // simply ask the popup for the desired URL directly.  As a result, the
481  // contents of this variable only need to be updated when the popup is closed
482  // but user_input_in_progress_ is not being cleared.
483  string16 url_for_remembered_user_selection_;
484
485  // Inline autocomplete is allowed if the user has not just deleted text, and
486  // no temporary text is showing.  In this case, inline_autocomplete_text_ is
487  // appended to the user_text_ and displayed selected (at least initially).
488  //
489  // NOTE: When the popup is closed there should never be inline autocomplete
490  // text (actions that close the popup should either accept the text, convert
491  // it to a normal selection, or change the edit entirely).
492  bool just_deleted_text_;
493  string16 inline_autocomplete_text_;
494
495  // Used by OnPopupDataChanged to keep track of whether there is currently a
496  // temporary text.
497  //
498  // Example of use: If the user types "goog", then arrows down in the
499  // autocomplete popup until, say, "google.com" appears in the edit box, then
500  // the user_text_ is still "goog", and "google.com" is "temporary text".
501  // When the user hits <esc>, the edit box reverts to "goog".  Hit <esc> again
502  // and the popup is closed and "goog" is replaced by the permanent_text_,
503  // which is the URL of the current page.
504  //
505  // original_url_ is only valid when there is temporary text, and is used as
506  // the unique identifier of the originally selected item.  Thus, if the user
507  // arrows to a different item with the same text, we can still distinguish
508  // them and not revert all the way to the permanent_text_.
509  bool has_temporary_text_;
510  GURL original_url_;
511
512  // True if Instant set the current temporary text, as opposed to it being set
513  // due to the user arrowing up/down through the popup. This can only be true
514  // if |has_temporary_text_| is true.
515  // TODO(sreeram): This is a temporary hack. Remove it once the omnibox edit
516  // model/view code is decoupled from Instant (among other things).
517  bool is_temporary_text_set_by_instant_;
518
519  // The index of the selected AutocompleteMatch in AutocompleteResult. This is
520  // needed to get the metadata details of the temporary text set by instant on
521  // the Local NTP. If the Instant extended is disabled or an Instant NTP is
522  // used, this is set to OmniboxPopupModel::kNoMatch.
523  size_t selected_instant_autocomplete_match_index_;
524
525  // True if the current temporary text set by Instant is a search query; false
526  // if it is a URL that can be directly navigated to. This is only valid if
527  // |is_temporary_text_set_by_instant_| is true. This field is needed because
528  // Instant's temporary text doesn't come from the popup model, so we can't
529  // lookup its type from the current match.
530  bool is_instant_temporary_text_a_search_query_;
531
532  // When the user's last action was to paste, we disallow inline autocomplete
533  // (on the theory that the user is trying to paste in a new URL or part of
534  // one, and in either case inline autocomplete would get in the way).
535  PasteState paste_state_;
536
537  // Whether the control key is depressed.  We track this to avoid calling
538  // UpdatePopup() repeatedly if the user holds down the key, and to know
539  // whether to trigger "ctrl-enter" behavior.
540  ControlKeyState control_key_state_;
541
542  // The keyword associated with the current match.  The user may have an actual
543  // selected keyword, or just some input text that looks like a keyword (so we
544  // can show a hint to press <tab>).  This is the keyword in either case;
545  // is_keyword_hint_ (below) distinguishes the two cases.
546  string16 keyword_;
547
548  // True if the keyword associated with this match is merely a hint, i.e. the
549  // user hasn't actually selected a keyword yet.  When this is true, we can use
550  // keyword_ to show a "Press <tab> to search" sort of hint.
551  bool is_keyword_hint_;
552
553  Profile* profile_;
554
555  // This is needed as prior to accepting the current text the model is
556  // reverted, which triggers resetting Instant. We don't want to update Instant
557  // in this case, so we use the flag to determine if this is happening.
558  bool in_revert_;
559
560  // InstantController needs this in extended mode to distinguish the case in
561  // which it should instruct a committed search results page to revert to
562  // showing results for the original query.
563  bool in_escape_handler_;
564
565  // Indicates if the upcoming autocomplete search is allowed to be treated as
566  // an exact keyword match.  If this is true then keyword mode will be
567  // triggered automatically if the input is "<keyword> <search string>".  We
568  // allow this when CreatedKeywordSearchByInsertingSpaceInMiddle() is true.
569  // This has no effect if we're already in keyword mode.
570  bool allow_exact_keyword_match_;
571
572  DISALLOW_COPY_AND_ASSIGN(OmniboxEditModel);
573};
574
575#endif  // CHROME_BROWSER_UI_OMNIBOX_OMNIBOX_EDIT_MODEL_H_
576