diff options
-rw-r--r-- | chrome/browser/autocomplete/autocomplete.h | 4 | ||||
-rw-r--r-- | chrome/browser/autocomplete/autocomplete_edit.cc | 8 | ||||
-rw-r--r-- | chrome/browser/autocomplete/autocomplete_edit.h | 11 | ||||
-rw-r--r-- | chrome/browser/autocomplete/autocomplete_popup.cc | 1205 | ||||
-rw-r--r-- | chrome/browser/autocomplete/autocomplete_popup.h | 384 |
5 files changed, 791 insertions, 821 deletions
diff --git a/chrome/browser/autocomplete/autocomplete.h b/chrome/browser/autocomplete/autocomplete.h index d7fd130..8102306 100644 --- a/chrome/browser/autocomplete/autocomplete.h +++ b/chrome/browser/autocomplete/autocomplete.h @@ -443,7 +443,7 @@ class AutocompleteProvider // // |minimal_changes| is an optimization that lets the provider do less work // when the |input|'s text hasn't changed. See the body of - // AutocompletePopup::StartAutocomplete(). + // AutocompletePopupModel::StartAutocomplete(). // // If |synchronous_only| is true, no asynchronous work should be scheduled; // the provider should stop after it has returned all the @@ -768,7 +768,7 @@ struct AutocompleteLog { } // The user's input text in the omnibox. std::wstring text; - // Selected index (if selected) or -1 (AutocompletePopup::kNoMatch). + // Selected index (if selected) or -1 (AutocompletePopupModel::kNoMatch). size_t selected_index; // Inline autocompleted length (if displayed). size_t inline_autocompleted_length; diff --git a/chrome/browser/autocomplete/autocomplete_edit.cc b/chrome/browser/autocomplete/autocomplete_edit.cc index ca18895..4c06a1c 100644 --- a/chrome/browser/autocomplete/autocomplete_edit.cc +++ b/chrome/browser/autocomplete/autocomplete_edit.cc @@ -125,7 +125,7 @@ AutocompleteEdit::AutocompleteEdit(const ChromeFont& font, bool popup_window_mode) : controller_(controller), model_(model), - popup_(new AutocompletePopup(font, this, profile)), + popup_(new AutocompletePopupModel(font, this, profile)), popup_window_mode_(popup_window_mode), has_focus_(false), user_input_in_progress_(false), @@ -455,7 +455,7 @@ void AutocompleteEdit::AcceptInput(WindowOpenDisposition disposition, } OpenURL(url, disposition, transition, alternate_nav_url, - AutocompletePopup::kNoMatch, + AutocompletePopupModel::kNoMatch, is_keyword_hint_ ? std::wstring() : keyword_); } @@ -2139,7 +2139,7 @@ void AutocompleteEdit::PasteAndGo() { // enough of an edge case that we ignore this possibility. RevertAll(); OpenURL(paste_and_go_url_, CURRENT_TAB, paste_and_go_transition_, - paste_and_go_alternate_nav_url_, AutocompletePopup::kNoMatch, + paste_and_go_alternate_nav_url_, AutocompletePopupModel::kNoMatch, std::wstring()); } @@ -2157,7 +2157,7 @@ void AutocompleteEdit::SendOpenNotification(size_t selected_line, // open). if (popup_->is_open()) { scoped_ptr<AutocompleteLog> log(popup_->GetAutocompleteLog()); - if (selected_line != AutocompletePopup::kNoMatch) + if (selected_line != AutocompletePopupModel::kNoMatch) log->selected_index = selected_line; else if (!has_temporary_text_) log->inline_autocompleted_length = inline_autocomplete_text_.length(); diff --git a/chrome/browser/autocomplete/autocomplete_edit.h b/chrome/browser/autocomplete/autocomplete_edit.h index 3b919d0..7d13409 100644 --- a/chrome/browser/autocomplete/autocomplete_edit.h +++ b/chrome/browser/autocomplete/autocomplete_edit.h @@ -20,7 +20,7 @@ #include "chrome/views/menu.h" #include "webkit/glue/window_open_disposition.h" -class AutocompletePopup; +class AutocompletePopupModel; class CommandController; class Profile; class TabContents; @@ -181,8 +181,7 @@ class AutocompleteEdit // |alternate_nav_url|, if non-empty, contains the alternate navigation URL // for |url|. See comments on AutocompleteResult::GetAlternateNavURL(). // - // |selected_line| is passed to AutocompletePopup::LogOpenedURL(); see - // comments there. + // |selected_line| is passed to SendOpenNotification(); see comments there. // // If the URL was expanded from a keyword, |keyword| is that keyword. // @@ -251,8 +250,8 @@ class AutocompleteEdit // send us events that we should treat as if they were events on us. void HandleExternalMsg(UINT msg, UINT flags, const CPoint& screen_point); - // Called back by the AutocompletePopup when any relevant data changes. This - // rolls together several separate pieces of data into one call so we can + // Called back by the AutocompletePopupModel when any relevant data changes. + // This rolls together several separate pieces of data into one call so we can // update all the UI efficiently: // |text| is either the new temporary text (if |is_temporary_text| is true) // from the user manually selecting a different match, or the inline @@ -577,7 +576,7 @@ class AutocompleteEdit Controller* controller_; // The Popup itself. - scoped_ptr<AutocompletePopup> popup_; + scoped_ptr<AutocompletePopupModel> popup_; // When true, the location bar view is read only and also is has a slightly // different presentation (font size / color). This is used for popups. diff --git a/chrome/browser/autocomplete/autocomplete_popup.cc b/chrome/browser/autocomplete/autocomplete_popup.cc index 087af29..b272143 100644 --- a/chrome/browser/autocomplete/autocomplete_popup.cc +++ b/chrome/browser/autocomplete/autocomplete_popup.cc @@ -21,16 +21,8 @@ #include "third_party/icu38/public/common/unicode/ubidi.h" namespace { -// The amount of time we'll wait after a provider returns before updating, -// in order to coalesce results. -const int kPopupCoalesceMs = 100; - -// The maximum time we'll allow the popup to go without updating. -const int kPopupUpdateMaxDelayMs = 300; - // Padding between text and the star indicator, in pixels. const int kStarPadding = 4; - }; // A simple wrapper class for the bidirectional iterator of ICU. @@ -38,51 +30,17 @@ const int kStarPadding = 4; // bidirectional texts into visual runs in its display order. class BiDiLineIterator { public: - BiDiLineIterator(); + BiDiLineIterator() : bidi_(NULL) { } ~BiDiLineIterator(); - // Initialize the bidirectional iterator with the specified text. - // Parameters - // * text [in] (const std::wstring&) - // Represents the text to be iterated with this iterator. - // * right_to_left [in] (bool) - // Represents whether or not the default text direction is right-to-left. - // Possible parameters are listed below: - // - true, the default text direction is right-to-left. - // - false, the default text direction is left-to-right. - // * url [in] (bool) - // Represents whether or not this text is a URL. - // Return values - // * true - // The bidirectional iterator is created successfully. - // * false - // An error occured while creating the bidirectional iterator. + // Initializes the bidirectional iterator with the specified text. Returns + // whether initialization succeeded. UBool Open(const std::wstring& text, bool right_to_left, bool url); - // Retrieve the number of visual runs in the text. - // Return values - // * A positive value - // Represents the number of visual runs. - // * 0 - // Represents an error. + // Returns the number of visual runs in the text, or zero on error. int CountRuns(); - // Get the logical offset, length, and direction of the specified visual run. - // Parameters - // * index [in] (int) - // Represents the index of the visual run. This value must be less than - // the return value of the CountRuns() function. - // * start [out] (int*) - // Represents the index of the specified visual run, in characters. - // * length [out] (int*) - // Represents the length of the specified visual run, in characters. - // Return values - // * UBIDI_LTR - // Represents this run should be rendered in the left-to-right reading - // order. - // * UBIDI_RTL - // Represents this run should be rendered in the right-to-left reading - // order. + // Gets the logical offset, length, and direction of the specified visual run. UBiDiDirection GetVisualRun(int index, int* start, int* length); private: @@ -91,10 +49,6 @@ class BiDiLineIterator { DISALLOW_EVIL_CONSTRUCTORS(BiDiLineIterator); }; -BiDiLineIterator::BiDiLineIterator() - : bidi_(NULL) { -} - BiDiLineIterator::~BiDiLineIterator() { if (bidi_) { ubidi_close(bidi_); @@ -136,13 +90,12 @@ UBiDiDirection BiDiLineIterator::GetVisualRun(int index, // application language is a right-to-left one. class MirroringContext { public: - MirroringContext(); - ~MirroringContext(); + MirroringContext() : min_x_(0), center_x_(0), max_x_(0), enabled_(false) { } // Initializes the bounding region used for mirroring coordinates. // This class uses the center of this region as an axis for calculating // mirrored coordinates. - void Initialize(int min_x, int max_x, bool enabled); + void Initialize(int x1, int x2, bool enabled); // Return the "left" side of the specified region. // When the application language is a right-to-left one, this function @@ -150,7 +103,7 @@ class MirroringContext { // left side of the mirrored region. // The input region must be in the bounding region specified in the // Initialize() function. - int GetLeft(int min_x, int max_x) const; + int GetLeft(int x1, int x2) const; // Returns whether or not we are mirroring the x coordinate. bool enabled() const { @@ -166,300 +119,157 @@ class MirroringContext { DISALLOW_EVIL_CONSTRUCTORS(MirroringContext); }; -MirroringContext::MirroringContext() - : min_x_(0), - center_x_(0), - max_x_(0), - enabled_(false) { -} - -MirroringContext::~MirroringContext() { -} - -void MirroringContext::Initialize(int min_x, int max_x, bool enabled) { - min_x_ = min_x; - max_x_ = max_x; - if (min_x_ > max_x_) - std::swap(min_x_, max_x_); - center_x_ = min_x + (max_x - min_x) / 2; +void MirroringContext::Initialize(int x1, int x2, bool enabled) { + min_x_ = std::min(x1, x2); + max_x_ = std::max(x1, x2); + center_x_ = min_x_ + (max_x_ - min_x_) / 2; enabled_ = enabled; } -int MirroringContext::GetLeft(int min_x, int max_x) const { - if (!enabled_) - return min_x; - int mirrored_min_x = center_x_ + (center_x_ - min_x); - int mirrored_max_x = center_x_ + (center_x_ - max_x); - return std::min(mirrored_min_x, mirrored_max_x); +int MirroringContext::GetLeft(int x1, int x2) const { + return enabled_ ? + (center_x_ + (center_x_ - std::max(x1, x2))) : std::min(x1, x2); } -const wchar_t AutocompletePopup::DrawLineInfo::ellipsis_str[] = L"\x2026"; +const wchar_t AutocompletePopupView::DrawLineInfo::ellipsis_str[] = L"\x2026"; -AutocompletePopup::AutocompletePopup(const ChromeFont& font, - AutocompleteEdit* editor, - Profile* profile) - : editor_(editor), - controller_(new AutocompleteController(this, profile)), - profile_(profile), +AutocompletePopupView::AutocompletePopupView(AutocompletePopupModel* model, + const ChromeFont& font) + : model_(model), line_info_(font), - query_in_progress_(false), - update_pending_(false), - // Creating the Timers directly instead of using StartTimer() ensures - // they won't actually start running until we use ResetTimer(). - coalesce_timer_(new Timer(kPopupCoalesceMs, this, false)), - max_delay_timer_(new Timer(kPopupUpdateMaxDelayMs, this, true)), - hovered_line_(kNoMatch), - selected_line_(kNoMatch), mirroring_context_(new MirroringContext()), - star_(ResourceBundle::GetSharedInstance().GetBitmapNamed(IDR_CONTENT_STAR_ON)) { + star_(ResourceBundle::GetSharedInstance().GetBitmapNamed( + IDR_CONTENT_STAR_ON)) { } -AutocompletePopup::~AutocompletePopup() { - StopAutocomplete(); -} +void AutocompletePopupView::InvalidateLine(size_t line) { + DCHECK(line < model_->result()->size()); -void AutocompletePopup::SetProfile(Profile* profile) { - DCHECK(profile); - profile_ = profile; - controller_->SetProfile(profile); + RECT rc; + GetClientRect(&rc); + rc.top = LineTopPixel(line); + rc.bottom = rc.top + line_info_.line_height; + InvalidateRect(&rc, false); } -void AutocompletePopup::StartAutocomplete( - const std::wstring& text, - const std::wstring& desired_tld, - bool prevent_inline_autocomplete) { - // The user is interacting with the edit, so stop tracking hover. - SetHoveredLine(kNoMatch); - - // See if we can avoid rerunning autocomplete when the query hasn't changed - // much. If the popup isn't open, we threw the past results away, so no - // shortcuts are possible. - const AutocompleteInput input(text, desired_tld, prevent_inline_autocomplete); - bool minimal_changes = false; - if (is_open()) { - // When the user hits escape with a temporary selection, the edit asks us - // to update, but the text it supplies hasn't changed since the last query. - // Instead of stopping or rerunning the last query, just do an immediate - // repaint with the new (probably NULL) provider affinity. - if (input_.Equals(input)) { - SetDefaultMatchAndUpdate(true); - return; +void AutocompletePopupView::UpdatePopupAppearance() { + if (model_->result()->empty()) { + // No results, close any existing popup. + if (m_hWnd) { + DestroyWindow(); + m_hWnd = NULL; } - - // When the user presses or releases the ctrl key, the desired_tld changes, - // and when the user finishes an IME composition, inline autocomplete may - // no longer be prevented. In both these cases the text itself hasn't - // changed since the last query, and some providers can do much less work - // (and get results back more quickly). Taking advantage of this reduces - // flicker. - if (input_.text() == text) - minimal_changes = true; - } - input_ = input; - - // If we're starting a brand new query, stop caring about any old query. - TimerManager* const tm = MessageLoop::current()->timer_manager(); - if (!minimal_changes && query_in_progress_) { - update_pending_ = false; - tm->StopTimer(coalesce_timer_.get()); - } - - // Start the new query. - query_in_progress_ = !controller_->Start(input_, minimal_changes, false); - controller_->GetResult(&latest_result_); - - // If we're not ready to show results and the max update interval timer isn't - // already running, start it now. - if (query_in_progress_ && !tm->IsTimerRunning(max_delay_timer_.get())) - tm->ResetTimer(max_delay_timer_.get()); - - SetDefaultMatchAndUpdate(!query_in_progress_); -} - -void AutocompletePopup::StopAutocomplete() { - // Close any old query. - if (query_in_progress_) { - controller_->Stop(); - query_in_progress_ = false; - update_pending_ = false; + return; } - // Reset results. This will force the popup to close. - latest_result_.Reset(); - CommitLatestResults(true); - - // Clear input_ to make sure we don't try and use any of these results for - // the next query we receive. Strictly speaking this isn't necessary, since - // the popup isn't open, but it keeps our internal state consistent and - // serves as future-proofing in case the code in StartAutocomplete() changes. - input_.Clear(); -} - -std::wstring AutocompletePopup::URLsForCurrentSelection( - PageTransition::Type* transition, - bool* is_history_what_you_typed_match, - std::wstring* alternate_nav_url) const { - // The popup may be out of date, and we always want the latest match. The - // most common case of this is when the popup is open, the user changes the - // contents of the edit, and then presses enter before any results have been - // displayed, but still wants to choose the would-be-default action. - // - // Can't call CommitLatestResults(), because - // latest_result_.default_match_index() may not match selected_line_, and - // we want to preserve the user's selection. - if (latest_result_.empty()) - return std::wstring(); + // Figure the coordinates of the popup: + // Get the coordinates of the location bar view; these are returned relative + // to its parent. + // TODO(pkasting): http://b/1345937 All this use of editor accessors should + // die once this class is a true ChromeView. + CRect rc; + model_->editor()->parent_view()->GetBounds(&rc); + // Subtract the top left corner to make the coordinates relative to the + // location bar view itself, and convert to screen coordinates. + CPoint top_left(-rc.TopLeft()); + ChromeViews::View::ConvertPointToScreen(model_->editor()->parent_view(), + &top_left); + rc.OffsetRect(top_left); + // Expand by one pixel on each side since that's the amount the location bar + // view is inset from the divider line that edges the adjacent buttons. + // Deflate the top and bottom by the height of the extra graphics around the + // edit. + // TODO(pkasting): http://b/972786 This shouldn't be hardcoded to rely on + // LocationBarView constants. Instead we should just make the edit be "at the + // right coordinates", or something else generic. + rc.InflateRect(1, -LocationBarView::kTextVertMargin); + // Now rc is the exact width we want and is positioned like the edit would + // be, so shift the top and bottom downwards so the new top is where the old + // bottom is and the rect has the height we need for all our entries, plus a + // one-pixel border on top and bottom. + rc.top = rc.bottom; + rc.bottom += + static_cast<int>(model_->result()->size()) * line_info_.line_height + 2; - const AutocompleteResult* result; - AutocompleteResult::const_iterator match; - if (update_pending_) { - // The default match on the latest result should be up-to-date. If the user - // changed the selection since that result was generated using the arrow - // keys, Move() will have force updated the popup. - result = &latest_result_; - match = result->default_match(); + if (!m_hWnd) { + // To prevent this window from being activated, we create an invisible + // window and manually show it without activating it. + Create(model_->editor()->m_hWnd, rc, AUTOCOMPLETEPOPUPVIEW_CLASSNAME, + WS_POPUP, WS_EX_TOOLWINDOW); + // When an IME is attached to the rich-edit control, retrieve its window + // handle and show this popup window under the IME windows. + // Otherwise, show this popup window under top-most windows. + // TODO(hbono): http://b/1111369 if we exclude this popup window from the + // display area of IME windows, this workaround becomes unnecessary. + HWND ime_window = ImmGetDefaultIMEWnd(model_->editor()->m_hWnd); + SetWindowPos(ime_window ? ime_window : HWND_NOTOPMOST, 0, 0, 0, 0, + SWP_NOSIZE | SWP_NOMOVE | SWP_NOACTIVATE | SWP_SHOWWINDOW); } else { - result = &result_; - DCHECK(selected_line_ < result_.size()); - match = result->begin() + selected_line_; + // Already open, just resize the window. This is a bit tricky; we want to + // repaint the whole window, since the contents may have changed, but + // MoveWindow() won't repaint portions that haven't moved or been + // added/removed. So we first call InvalidateRect(), so the next repaint + // paints the whole window, then tell MoveWindow() to do the actual + // repaint, which will also properly repaint Windows formerly under the + // popup. + InvalidateRect(NULL, false); + MoveWindow(rc.left, rc.top, rc.right - rc.left, rc.bottom - rc.top, true); } - if (transition) - *transition = match->transition; - if (is_history_what_you_typed_match) - *is_history_what_you_typed_match = match->is_history_what_you_typed_match; - if (alternate_nav_url && manually_selected_match_.empty()) - *alternate_nav_url = result->GetAlternateNavURL(input_, match); - return match->destination_url; -} - -std::wstring AutocompletePopup::URLsForDefaultMatch( - const std::wstring& text, - const std::wstring& desired_tld, - PageTransition::Type* transition, - bool* is_history_what_you_typed_match, - std::wstring* alternate_nav_url) { - // Cancel any existing query. - StopAutocomplete(); - - // Run the new query and get only the synchronously available results. - const AutocompleteInput input(text, desired_tld, true); - const bool done = controller_->Start(input, false, true); - DCHECK(done); - controller_->GetResult(&result_); - if (result_.empty()) - return std::wstring(); - // Get the URLs for the default match. - result_.SetDefaultMatch(manually_selected_match_); - const AutocompleteResult::const_iterator match = result_.default_match(); - const std::wstring url(match->destination_url); // Need to copy since we - // reset result_ below. - if (transition) - *transition = match->transition; - if (is_history_what_you_typed_match) - *is_history_what_you_typed_match = match->is_history_what_you_typed_match; - if (alternate_nav_url && manually_selected_match_.empty()) - *alternate_nav_url = result_.GetAlternateNavURL(input, match); - result_.Reset(); - - return url; -} - -AutocompleteLog* AutocompletePopup::GetAutocompleteLog() { - return new AutocompleteLog(input_.text(), selected_line_, 0, result_); + // TODO(pkasting): http://b/1111369 We should call ImmSetCandidateWindow() on + // the model_->editor()'s IME context here, and exclude ourselves from its + // display area. Not clear what to pass for the lpCandidate->ptCurrentPos + // member, though... } -void AutocompletePopup::Move(int count) { - // The user is using the keyboard to change the selection, so stop tracking - // hover. - SetHoveredLine(kNoMatch); - - // Force the popup to open/update, so the user is interacting with the - // latest results. - CommitLatestResults(false); - if (result_.empty()) - return; - - // Clamp the new line to [0, result_.count() - 1]. - const size_t new_line = selected_line_ + count; - SetSelectedLine(((count < 0) && (new_line >= selected_line_)) ? - 0 : std::min(new_line, result_.size() - 1)); -} +void AutocompletePopupView::OnHoverEnabledOrDisabled(bool disabled) { + TRACKMOUSEEVENT tme; + tme.cbSize = sizeof(TRACKMOUSEEVENT); + if (disabled) { + // Save the current mouse position to check against for re-enabling. + GetCursorPos(&last_hover_coordinates_); // Returns screen coordinates -void AutocompletePopup::TryDeletingCurrentItem() { - // We could use URLsForCurrentSelection() here, but it seems better to try - // and shift-delete the actual selection, rather than any "in progress, not - // yet visible" one. - if (selected_line_ == kNoMatch) - return; - const AutocompleteMatch& match = result_.match_at(selected_line_); - if (match.deletable) { - query_in_progress_ = true; - size_t selected_line = selected_line_; - match.provider->DeleteMatch(match); // This will synchronously call back - // to OnAutocompleteUpdate() - CommitLatestResults(false); - if (!result_.empty()) { - // Move the selection to the next choice after the deleted one, but clear - // the manual selection so this choice doesn't act "sticky". - // - // It might also be correct to only call Clear() here when - // manually_selected_match_ didn't already have a provider() (i.e. when - // there was no existing manual selection). It's not clear what the user - // wants when they shift-delete something they've arrowed to. If they - // arrowed down just to shift-delete it, we should probably Clear(); if - // they arrowed to do something else, then got a bad match while typing, - // we probably shouldn't. - SetSelectedLine(std::min(result_.size() - 1, selected_line)); - manually_selected_match_.Clear(); - } + // Cancel existing registration for WM_MOUSELEAVE notifications. + tme.dwFlags = TME_CANCEL | TME_LEAVE; + } else { + // Register for WM_MOUSELEAVE notifications. + tme.dwFlags = TME_LEAVE; } + tme.hwndTrack = m_hWnd; + tme.dwHoverTime = HOVER_DEFAULT; // Not actually used + TrackMouseEvent(&tme); } -void AutocompletePopup::OnAutocompleteUpdate(bool updated_result, - bool query_complete) { - DCHECK(query_in_progress_); - if (updated_result) - controller_->GetResult(&latest_result_); - query_in_progress_ = !query_complete; - - SetDefaultMatchAndUpdate(query_complete); -} - -void AutocompletePopup::Run() { - CommitLatestResults(false); -} - -void AutocompletePopup::OnLButtonDown(UINT keys, const CPoint& point) { +void AutocompletePopupView::OnLButtonDown(UINT keys, const CPoint& point) { const size_t new_hovered_line = PixelToLine(point.y); - SetHoveredLine(new_hovered_line); - SetSelectedLine(new_hovered_line); + model_->SetHoveredLine(new_hovered_line); + model_->SetSelectedLine(new_hovered_line); } -void AutocompletePopup::OnMButtonDown(UINT keys, const CPoint& point) { - SetHoveredLine(PixelToLine(point.y)); +void AutocompletePopupView::OnMButtonDown(UINT keys, const CPoint& point) { + model_->SetHoveredLine(PixelToLine(point.y)); } -void AutocompletePopup::OnLButtonUp(UINT keys, const CPoint& point) { +void AutocompletePopupView::OnLButtonUp(UINT keys, const CPoint& point) { OnButtonUp(point, CURRENT_TAB); } -void AutocompletePopup::OnMButtonUp(UINT keys, const CPoint& point) { +void AutocompletePopupView::OnMButtonUp(UINT keys, const CPoint& point) { OnButtonUp(point, NEW_BACKGROUND_TAB); } -LRESULT AutocompletePopup::OnMouseActivate(HWND window, - UINT hit_test, - UINT mouse_message) { +LRESULT AutocompletePopupView::OnMouseActivate(HWND window, + UINT hit_test, + UINT mouse_message) { return MA_NOACTIVATE; } -void AutocompletePopup::OnMouseLeave() { +void AutocompletePopupView::OnMouseLeave() { // The mouse has left the window, so no line is hovered. - SetHoveredLine(kNoMatch); + model_->SetHoveredLine(AutocompletePopupModel::kNoMatch); } -void AutocompletePopup::OnMouseMove(UINT keys, const CPoint& point) { +void AutocompletePopupView::OnMouseMove(UINT keys, const CPoint& point) { // Track hover when // (a) The left or middle button is down (the user is interacting via the // mouse) @@ -470,22 +280,22 @@ void AutocompletePopup::OnMouseMove(UINT keys, const CPoint& point) { CPoint screen_point(point); ClientToScreen(&screen_point); if (action_button_pressed || (last_hover_coordinates_ != screen_point) || - (hovered_line_ != kNoMatch)) { + (model_->hovered_line() != AutocompletePopupModel::kNoMatch)) { // Determine the hovered line from the y coordinate of the event. We don't // need to check whether the x coordinates are within the window since if // they weren't someone else would have received the WM_MOUSEMOVE. const size_t new_hovered_line = PixelToLine(point.y); - SetHoveredLine(new_hovered_line); + model_->SetHoveredLine(new_hovered_line); // When the user has the left button down, update their selection // immediately (don't wait for mouseup). if (keys & MK_LBUTTON) - SetSelectedLine(new_hovered_line); + model_->SetSelectedLine(new_hovered_line); } } -void AutocompletePopup::OnPaint(HDC other_dc) { - DCHECK(!result_.empty()); // Shouldn't be drawing an empty popup +void AutocompletePopupView::OnPaint(HDC other_dc) { + DCHECK(!model_->result()->empty()); // Shouldn't be drawing an empty popup CPaintDC dc(m_hWnd); @@ -496,8 +306,8 @@ void AutocompletePopup::OnPaint(HDC other_dc) { DrawBorder(rc, dc); bool all_descriptions_empty = true; - for (AutocompleteResult::const_iterator i(result_.begin()); - i != result_.end(); ++i) { + for (AutocompleteResult::const_iterator i(model_->result()->begin()); + i != model_->result()->end(); ++i) { if (!i->description.empty()) { all_descriptions_empty = false; break; @@ -510,273 +320,36 @@ void AutocompletePopup::OnPaint(HDC other_dc) { for (size_t i = first_line; i <= last_line; ++i) { DrawLineInfo::LineStatus status; // Selection should take precedence over hover. - if (i == selected_line_) + if (i == model_->selected_line()) status = DrawLineInfo::SELECTED; - else if (i == hovered_line_) + else if (i == model_->hovered_line()) status = DrawLineInfo::HOVERED; else status = DrawLineInfo::NORMAL; DrawEntry(dc, rc, i, status, all_descriptions_empty, - result_.match_at(i).starred); - } -} - -void AutocompletePopup::OnButtonUp(const CPoint& point, - WindowOpenDisposition disposition) { - const size_t selected_line = PixelToLine(point.y); - const AutocompleteMatch& match = result_.match_at(selected_line); - // OpenURL() may close the popup, which will clear the result set and, by - // extension, |match| and its contents. So copy the relevant strings out to - // make sure they stay alive until the call completes. - const std::wstring url(match.destination_url); - std::wstring keyword; - const bool is_keyword_hint = GetKeywordForMatch(match, &keyword); - editor_->OpenURL(url, disposition, match.transition, std::wstring(), - selected_line, is_keyword_hint ? std::wstring() : keyword); -} - -void AutocompletePopup::SetDefaultMatchAndUpdate(bool immediately) { - if (!latest_result_.SetDefaultMatch(manually_selected_match_) && - !query_in_progress_) { - // We don't clear the provider affinity because the user didn't do - // something to indicate that they want a different provider, we just - // couldn't find the specific match they wanted. - manually_selected_match_.destination_url.clear(); - manually_selected_match_.is_history_what_you_typed_match = false; - } - - if (immediately) { - CommitLatestResults(true); - } else if (!update_pending_) { - // Coalesce the results for the next kPopupCoalesceMs milliseconds. - update_pending_ = true; - MessageLoop::current()->timer_manager()->ResetTimer(coalesce_timer_.get()); + model_->result()->match_at(i).starred); } - - // Update the edit with the possibly new data for this match. - // NOTE: This must be done after the code above, so that our internal state - // will be consistent when the edit calls back to URLsForCurrentSelection(). - std::wstring inline_autocomplete_text; - std::wstring keyword; - bool is_keyword_hint = false; - bool can_show_search_hint = true; - const AutocompleteResult::const_iterator match( - latest_result_.default_match()); - if (match != latest_result_.end()) { - if ((match->inline_autocomplete_offset != std::wstring::npos) && - (match->inline_autocomplete_offset < match->fill_into_edit.length())) { - inline_autocomplete_text = - match->fill_into_edit.substr(match->inline_autocomplete_offset); - } - // Warm up DNS Prefetch Cache. - chrome_browser_net::DnsPrefetchUrlString(match->destination_url); - // We could prefetch the alternate nav URL, if any, but because there can be - // many of these as a user types an initial series of characters, the OS DNS - // cache could suffer eviction problems for minimal gain. - - is_keyword_hint = GetKeywordForMatch(*match, &keyword); - can_show_search_hint = (match->type == AutocompleteMatch::SEARCH); - } - editor_->OnPopupDataChanged(inline_autocomplete_text, false, - manually_selected_match_ /* ignored */, keyword, is_keyword_hint, - can_show_search_hint); -} - -void AutocompletePopup::CommitLatestResults(bool force) { - if (!force && !update_pending_) - return; - - update_pending_ = false; - - // If we're going to trim the window size to no longer include the hovered - // line, turn hover off first. We need to do this before changing result_ - // since SetHover() should be able to trust that the old and new hovered - // lines are valid. - // - // Practically, this only currently happens when we're closing the window by - // setting latest_result_ to an empty list. - if ((hovered_line_ != kNoMatch) && (latest_result_.size() <= hovered_line_)) - SetHoveredLine(kNoMatch); - - result_.CopyFrom(latest_result_); - selected_line_ = (result_.default_match() == result_.end()) ? - kNoMatch : (result_.default_match() - result_.begin()); - - UpdatePopupAppearance(); - - // The max update interval timer either needs to be reset (if more updates - // are to come) or stopped (when we're done with the query). The coalesce - // timer should always just be stopped. - TimerManager* const tm = MessageLoop::current()->timer_manager(); - tm->StopTimer(coalesce_timer_.get()); - if (query_in_progress_) - tm->ResetTimer(max_delay_timer_.get()); - else - tm->StopTimer(max_delay_timer_.get()); } -void AutocompletePopup::UpdatePopupAppearance() { - if (result_.empty()) { - // No results, close any existing popup. - if (m_hWnd) { - DestroyWindow(); - m_hWnd = NULL; - } - return; - } - - // Figure the coordinates of the popup: - // Get the coordinates of the location bar view; these are returned relative - // to its parent. - CRect rc; - editor_->parent_view()->GetBounds(&rc); - // Subtract the top left corner to make the coordinates relative to the - // location bar view itself, and convert to screen coordinates. - CPoint top_left(-rc.TopLeft()); - ChromeViews::View::ConvertPointToScreen(editor_->parent_view(), &top_left); - rc.OffsetRect(top_left); - // Expand by one pixel on each side since that's the amount the location bar - // view is inset from the divider line that edges the adjacent buttons. - // Deflate the top and bottom by the height of the extra graphics around the - // edit. - // TODO(pkasting): http://b/972786 This shouldn't be hardcoded to rely on - // LocationBarView constants. Instead we should just make the edit be "at the - // right coordinates", or something else generic. - rc.InflateRect(1, -LocationBarView::kTextVertMargin); - // Now rc is the exact width we want and is positioned like the edit would - // be, so shift the top and bottom downwards so the new top is where the old - // bottom is and the rect has the height we need for all our entries, plus a - // one-pixel border on top and bottom. - rc.top = rc.bottom; - rc.bottom += static_cast<int>(result_.size()) * line_info_.line_height + 2; - - if (!m_hWnd) { - // To prevent this window from being activated, we create an invisible - // window and manually show it without activating it. - Create(editor_->m_hWnd, rc, AUTOCOMPLETEPOPUP_CLASSNAME, WS_POPUP, - WS_EX_TOOLWINDOW); - // When an IME is attached to the rich-edit control, retrieve its window - // handle and show this popup window under the IME windows. - // Otherwise, show this popup window under top-most windows. - // TODO(hbono): http://b/1111369 if we exclude this popup window from the - // display area of IME windows, this workaround becomes unnecessary. - HWND ime_window = ImmGetDefaultIMEWnd(editor_->m_hWnd); - SetWindowPos(ime_window ? ime_window : HWND_NOTOPMOST, 0, 0, 0, 0, - SWP_NOSIZE | SWP_NOMOVE | SWP_NOACTIVATE | SWP_SHOWWINDOW); - } else { - // Already open, just resize the window. This is a bit tricky; we want to - // repaint the whole window, since the contents may have changed, but - // MoveWindow() won't repaint portions that haven't moved or been - // added/removed. So we first call InvalidateRect(), so the next repaint - // paints the whole window, then tell MoveWindow() to do the actual - // repaint, which will also properly repaint Windows formerly under the - // popup. - InvalidateRect(NULL, false); - MoveWindow(rc.left, rc.top, rc.right - rc.left, rc.bottom - rc.top, true); - } - - // TODO(pkasting): http://b/1111369 We should call ImmSetCandidateWindow() on - // the editor_'s IME context here, and exclude ourselves from its display - // area. Not clear what to pass for the lpCandidate->ptCurrentPos member, - // though... +void AutocompletePopupView::OnButtonUp(const CPoint& point, + WindowOpenDisposition disposition) { + model_->OpenLine(PixelToLine(point.y), disposition); } -int AutocompletePopup::LineTopPixel(size_t line) const { - DCHECK(line <= result_.size()); +int AutocompletePopupView::LineTopPixel(size_t line) const { + DCHECK(line <= model_->result()->size()); // The popup has a 1 px top border. return line_info_.line_height * static_cast<int>(line) + 1; } -size_t AutocompletePopup::PixelToLine(int y) const { +size_t AutocompletePopupView::PixelToLine(int y) const { const size_t line = std::max(y - 1, 0) / line_info_.line_height; - return (line < result_.size()) ? line : (result_.size() - 1); -} - -void AutocompletePopup::SetHoveredLine(size_t line) { - const bool is_disabling = (line == kNoMatch); - DCHECK(is_disabling || (line < result_.size())); - - if (line == hovered_line_) - return; // Nothing to do - - const bool is_enabling = (hovered_line_ == kNoMatch); - if (is_enabling || is_disabling) { - TRACKMOUSEEVENT tme; - tme.cbSize = sizeof(TRACKMOUSEEVENT); - if (is_disabling) { - // Save the current mouse position to check against for re-enabling. - GetCursorPos(&last_hover_coordinates_); // Returns screen coordinates - - // Cancel existing registration for WM_MOUSELEAVE notifications. - tme.dwFlags = TME_CANCEL | TME_LEAVE; - } else { - // Register for WM_MOUSELEAVE notifications. - tme.dwFlags = TME_LEAVE; - } - tme.hwndTrack = m_hWnd; - tme.dwHoverTime = HOVER_DEFAULT; // Not actually used - TrackMouseEvent(&tme); - } - - // Make sure the old hovered line is redrawn. No need to redraw the selected - // line since selection overrides hover so the appearance won't change. - if (!is_enabling && (hovered_line_ != selected_line_)) - InvalidateLine(hovered_line_); - - // Change the hover to the new line and make sure it's redrawn. - hovered_line_ = line; - if (!is_disabling && (hovered_line_ != selected_line_)) - InvalidateLine(hovered_line_); -} - -void AutocompletePopup::SetSelectedLine(size_t line) { - DCHECK(line < result_.size()); - if (result_.empty()) - return; - - if (line == selected_line_) - return; // Nothing to do - - // Update the edit with the new data for this match. - const AutocompleteMatch& match = result_.match_at(line); - std::wstring keyword; - const bool is_keyword_hint = GetKeywordForMatch(match, &keyword); - editor_->OnPopupDataChanged(match.fill_into_edit, true, - manually_selected_match_, keyword, - is_keyword_hint, - (match.type == AutocompleteMatch::SEARCH)); - - // Track the user's selection until they cancel it. - manually_selected_match_.destination_url = match.destination_url; - manually_selected_match_.provider_affinity = match.provider; - manually_selected_match_.is_history_what_you_typed_match = - match.is_history_what_you_typed_match; - - // Repaint old and new selected lines immediately, so that the edit doesn't - // appear to update [much] faster than the popup. We must not update - // |selected_line_| before calling OnPopupDataChanged() (since the edit may - // call us back to get data about the old selection), and we must not call - // UpdateWindow() before updating |selected_line_| (since the paint routine - // relies on knowing the correct selected line). - InvalidateLine(selected_line_); - selected_line_ = line; - InvalidateLine(selected_line_); - UpdateWindow(); -} - -void AutocompletePopup::InvalidateLine(size_t line) { - DCHECK(line < result_.size()); - - RECT rc; - GetClientRect(&rc); - rc.top = LineTopPixel(line); - rc.bottom = rc.top + line_info_.line_height; - InvalidateRect(&rc, false); + return std::min(line, model_->result()->size() - 1); } // Draws a light border around the inside of the window with the given client // rectangle and DC. -void AutocompletePopup::DrawBorder(const RECT& rc, HDC dc) { +void AutocompletePopupView::DrawBorder(const RECT& rc, HDC dc) { HPEN hpen = CreatePen(PS_SOLID, 1, RGB(199, 202, 206)); HGDIOBJ old_pen = SelectObject(dc, hpen); @@ -793,16 +366,16 @@ void AutocompletePopup::DrawBorder(const RECT& rc, HDC dc) { DeleteObject(hpen); } -int AutocompletePopup::DrawString(HDC dc, - int x, - int y, - int max_x, - const wchar_t* text, - int length, - int style, - const DrawLineInfo::LineStatus status, - const MirroringContext* context, - bool text_direction_is_rtl) const { +int AutocompletePopupView::DrawString(HDC dc, + int x, + int y, + int max_x, + const wchar_t* text, + int length, + int style, + const DrawLineInfo::LineStatus status, + const MirroringContext* context, + bool text_direction_is_rtl) const { if (length <= 0) return 0; @@ -858,7 +431,7 @@ int AutocompletePopup::DrawString(HDC dc, return text_x - x; } -void AutocompletePopup::DrawMatchFragments( +void AutocompletePopupView::DrawMatchFragments( HDC dc, const std::wstring& text, const ACMatchClassifications& classifications, @@ -963,12 +536,12 @@ void AutocompletePopup::DrawMatchFragments( } } -void AutocompletePopup::DrawEntry(HDC dc, - const RECT& client_rect, - size_t line, - DrawLineInfo::LineStatus status, - bool all_descriptions_empty, - bool starred) const { +void AutocompletePopupView::DrawEntry(HDC dc, + const RECT& client_rect, + size_t line, + DrawLineInfo::LineStatus status, + bool all_descriptions_empty, + bool starred) const { // Calculate outer bounds of entry, and fill background. const int top_pixel = LineTopPixel(line); const RECT rc = {1, top_pixel, client_rect.right - client_rect.left - 1, @@ -1004,7 +577,7 @@ void AutocompletePopup::DrawEntry(HDC dc, // the HISTORY_SEARCH shortcut, the description section is eliminated, and // all the available width is used for the content section. int star_x; - const AutocompleteMatch& match = result_.match_at(line); + const AutocompleteMatch& match = model_->result()->match_at(line); if ((description_width < (line_info_.ave_char_width * 20)) || all_descriptions_empty || (match.type == AutocompleteMatch::HISTORY_SEARCH)) { @@ -1024,7 +597,7 @@ void AutocompletePopup::DrawEntry(HDC dc, (line_info_.line_height - star_->height()) / 2 + top_pixel); } -void AutocompletePopup::DrawStar(HDC dc, int x, int y) const { +void AutocompletePopupView::DrawStar(HDC dc, int x, int y) const { ChromeCanvas canvas(star_->width(), star_->height(), false); // Make the background completely transparent. canvas.drawColor(SK_ColorBLACK, SkPorterDuff::kClear_Mode); @@ -1033,39 +606,7 @@ void AutocompletePopup::DrawStar(HDC dc, int x, int y) const { dc, mirroring_context_->GetLeft(x, x + star_->width()), y, NULL); } -bool AutocompletePopup::GetKeywordForMatch(const AutocompleteMatch& match, - std::wstring* keyword) { - // Assume we have no keyword until we find otherwise. - keyword->clear(); - - // If the current match is a keyword, return that as the selected keyword. - if (match.template_url && match.template_url->url() && - match.template_url->url()->SupportsReplacement()) { - keyword->assign(match.template_url->keyword()); - return false; - } - - // See if the current match's fill_into_edit corresponds to a keyword. - if (!profile_->GetTemplateURLModel()) - return false; - profile_->GetTemplateURLModel()->Load(); - const std::wstring keyword_hint( - TemplateURLModel::CleanUserInputKeyword(match.fill_into_edit)); - if (keyword_hint.empty()) - return false; - - // Don't provide a hint if this keyword doesn't support replacement. - const TemplateURL* const template_url = - profile_->GetTemplateURLModel()->GetTemplateURLForKeyword(keyword_hint); - if (!template_url || !template_url->url() || - !template_url->url()->SupportsReplacement()) - return false; - - keyword->assign(keyword_hint); - return true; -} - -AutocompletePopup::DrawLineInfo::DrawLineInfo(const ChromeFont& font) { +AutocompletePopupView::DrawLineInfo::DrawLineInfo(const ChromeFont& font) { // Create regular and bold fonts. regular_font = font.DeriveFont(-1); bold_font = regular_font.DeriveFont(0, ChromeFont::BOLD); @@ -1105,14 +646,15 @@ AutocompletePopup::DrawLineInfo::DrawLineInfo(const ChromeFont& font) { } } -AutocompletePopup::DrawLineInfo::~DrawLineInfo() { +AutocompletePopupView::DrawLineInfo::~DrawLineInfo() { for (int i = 0; i < MAX_STATUS_ENTRIES; ++i) DeleteObject(brushes[i]); } // static -double AutocompletePopup::DrawLineInfo::LuminosityContrast(COLORREF color1, - COLORREF color2) { +double AutocompletePopupView::DrawLineInfo::LuminosityContrast( + COLORREF color1, + COLORREF color2) { // This algorithm was adapted from the following text at // http://juicystudio.com/article/luminositycontrastratioalgorithm.php : // @@ -1132,7 +674,7 @@ double AutocompletePopup::DrawLineInfo::LuminosityContrast(COLORREF color1, } // static -double AutocompletePopup::DrawLineInfo::Luminosity(COLORREF color) { +double AutocompletePopupView::DrawLineInfo::Luminosity(COLORREF color) { // See comments in LuminosityContrast(). const double linearised_r = pow(static_cast<double>(GetRValue(color)) / 255.0, 2.2); @@ -1144,9 +686,9 @@ double AutocompletePopup::DrawLineInfo::Luminosity(COLORREF color) { (0.0722 * linearised_b); } -COLORREF AutocompletePopup::DrawLineInfo::AlphaBlend(COLORREF foreground, - COLORREF background, - BYTE alpha) { +COLORREF AutocompletePopupView::DrawLineInfo::AlphaBlend(COLORREF foreground, + COLORREF background, + BYTE alpha) { if (alpha == 0) return background; else if (alpha == 0xff) @@ -1161,3 +703,426 @@ COLORREF AutocompletePopup::DrawLineInfo::AlphaBlend(COLORREF foreground, (GetBValue(background) * (0xff - alpha))) / 0xff); } +namespace { +// The amount of time we'll wait after a provider returns before updating, +// in order to coalesce results. +const int kPopupCoalesceMs = 100; + +// The maximum time we'll allow the popup to go without updating. +const int kPopupUpdateMaxDelayMs = 300; +}; + +AutocompletePopupModel::AutocompletePopupModel(const ChromeFont& font, + AutocompleteEdit* editor, + Profile* profile) + : view_(new AutocompletePopupView(this, font)), + editor_(editor), + controller_(new AutocompleteController(this, profile)), + profile_(profile), + query_in_progress_(false), + update_pending_(false), + // Creating the Timers directly instead of using StartTimer() ensures + // they won't actually start running until we use ResetTimer(). + coalesce_timer_(new Timer(kPopupCoalesceMs, this, false)), + max_delay_timer_(new Timer(kPopupUpdateMaxDelayMs, this, true)), + hovered_line_(kNoMatch), + selected_line_(kNoMatch) { +} + +AutocompletePopupModel::~AutocompletePopupModel() { + StopAutocomplete(); +} + +void AutocompletePopupModel::SetProfile(Profile* profile) { + DCHECK(profile); + profile_ = profile; + controller_->SetProfile(profile); +} + +void AutocompletePopupModel::StartAutocomplete( + const std::wstring& text, + const std::wstring& desired_tld, + bool prevent_inline_autocomplete) { + // The user is interacting with the edit, so stop tracking hover. + SetHoveredLine(kNoMatch); + + // See if we can avoid rerunning autocomplete when the query hasn't changed + // much. If the popup isn't open, we threw the past results away, so no + // shortcuts are possible. + const AutocompleteInput input(text, desired_tld, prevent_inline_autocomplete); + bool minimal_changes = false; + if (is_open()) { + // When the user hits escape with a temporary selection, the edit asks us + // to update, but the text it supplies hasn't changed since the last query. + // Instead of stopping or rerunning the last query, just do an immediate + // repaint with the new (probably NULL) provider affinity. + if (input_.Equals(input)) { + SetDefaultMatchAndUpdate(true); + return; + } + + // When the user presses or releases the ctrl key, the desired_tld changes, + // and when the user finishes an IME composition, inline autocomplete may + // no longer be prevented. In both these cases the text itself hasn't + // changed since the last query, and some providers can do much less work + // (and get results back more quickly). Taking advantage of this reduces + // flicker. + if (input_.text() == text) + minimal_changes = true; + } + input_ = input; + + // If we're starting a brand new query, stop caring about any old query. + TimerManager* const tm = MessageLoop::current()->timer_manager(); + if (!minimal_changes && query_in_progress_) { + update_pending_ = false; + tm->StopTimer(coalesce_timer_.get()); + } + + // Start the new query. + query_in_progress_ = !controller_->Start(input_, minimal_changes, false); + controller_->GetResult(&latest_result_); + + // If we're not ready to show results and the max update interval timer isn't + // already running, start it now. + if (query_in_progress_ && !tm->IsTimerRunning(max_delay_timer_.get())) + tm->ResetTimer(max_delay_timer_.get()); + + SetDefaultMatchAndUpdate(!query_in_progress_); +} + +void AutocompletePopupModel::StopAutocomplete() { + // Close any old query. + if (query_in_progress_) { + controller_->Stop(); + query_in_progress_ = false; + update_pending_ = false; + } + + // Reset results. This will force the popup to close. + latest_result_.Reset(); + CommitLatestResults(true); + + // Clear input_ to make sure we don't try and use any of these results for + // the next query we receive. Strictly speaking this isn't necessary, since + // the popup isn't open, but it keeps our internal state consistent and + // serves as future-proofing in case the code in StartAutocomplete() changes. + input_.Clear(); +} + +void AutocompletePopupModel::SetHoveredLine(size_t line) { + const bool is_disabling = (line == kNoMatch); + DCHECK(is_disabling || (line < result_.size())); + + if (line == hovered_line_) + return; // Nothing to do + + // Make sure the old hovered line is redrawn. No need to redraw the selected + // line since selection overrides hover so the appearance won't change. + const bool is_enabling = (hovered_line_ == kNoMatch); + if (!is_enabling && (hovered_line_ != selected_line_)) + view_->InvalidateLine(hovered_line_); + + // Change the hover to the new line and make sure it's redrawn. + hovered_line_ = line; + if (!is_disabling && (hovered_line_ != selected_line_)) + view_->InvalidateLine(hovered_line_); + + if (is_enabling || is_disabling) + view_->OnHoverEnabledOrDisabled(is_disabling); +} + +void AutocompletePopupModel::SetSelectedLine(size_t line) { + DCHECK(line < result_.size()); + if (result_.empty()) + return; + + if (line == selected_line_) + return; // Nothing to do + + // Update the edit with the new data for this match. + const AutocompleteMatch& match = result_.match_at(line); + std::wstring keyword; + const bool is_keyword_hint = GetKeywordForMatch(match, &keyword); + editor_->OnPopupDataChanged(match.fill_into_edit, true, + manually_selected_match_, keyword, + is_keyword_hint, + (match.type == AutocompleteMatch::SEARCH)); + + // Track the user's selection until they cancel it. + manually_selected_match_.destination_url = match.destination_url; + manually_selected_match_.provider_affinity = match.provider; + manually_selected_match_.is_history_what_you_typed_match = + match.is_history_what_you_typed_match; + + // Repaint old and new selected lines immediately, so that the edit doesn't + // appear to update [much] faster than the popup. We must not update + // |selected_line_| before calling OnPopupDataChanged() (since the edit may + // call us back to get data about the old selection), and we must not call + // UpdateWindow() before updating |selected_line_| (since the paint routine + // relies on knowing the correct selected line). + view_->InvalidateLine(selected_line_); + selected_line_ = line; + view_->InvalidateLine(selected_line_); + view_->UpdateWindow(); +} + +std::wstring AutocompletePopupModel::URLsForCurrentSelection( + PageTransition::Type* transition, + bool* is_history_what_you_typed_match, + std::wstring* alternate_nav_url) const { + // The popup may be out of date, and we always want the latest match. The + // most common case of this is when the popup is open, the user changes the + // contents of the edit, and then presses enter before any results have been + // displayed, but still wants to choose the would-be-default action. + // + // Can't call CommitLatestResults(), because + // latest_result_.default_match_index() may not match selected_line_, and + // we want to preserve the user's selection. + if (latest_result_.empty()) + return std::wstring(); + + const AutocompleteResult* result; + AutocompleteResult::const_iterator match; + if (update_pending_) { + // The default match on the latest result should be up-to-date. If the user + // changed the selection since that result was generated using the arrow + // keys, Move() will have force updated the popup. + result = &latest_result_; + match = result->default_match(); + } else { + result = &result_; + DCHECK(selected_line_ < result_.size()); + match = result->begin() + selected_line_; + } + if (transition) + *transition = match->transition; + if (is_history_what_you_typed_match) + *is_history_what_you_typed_match = match->is_history_what_you_typed_match; + if (alternate_nav_url && manually_selected_match_.empty()) + *alternate_nav_url = result->GetAlternateNavURL(input_, match); + return match->destination_url; +} + +std::wstring AutocompletePopupModel::URLsForDefaultMatch( + const std::wstring& text, + const std::wstring& desired_tld, + PageTransition::Type* transition, + bool* is_history_what_you_typed_match, + std::wstring* alternate_nav_url) { + // Cancel any existing query. + StopAutocomplete(); + + // Run the new query and get only the synchronously available results. + const AutocompleteInput input(text, desired_tld, true); + const bool done = controller_->Start(input, false, true); + DCHECK(done); + controller_->GetResult(&result_); + if (result_.empty()) + return std::wstring(); + + // Get the URLs for the default match. + result_.SetDefaultMatch(manually_selected_match_); + const AutocompleteResult::const_iterator match = result_.default_match(); + const std::wstring url(match->destination_url); // Need to copy since we + // reset result_ below. + if (transition) + *transition = match->transition; + if (is_history_what_you_typed_match) + *is_history_what_you_typed_match = match->is_history_what_you_typed_match; + if (alternate_nav_url && manually_selected_match_.empty()) + *alternate_nav_url = result_.GetAlternateNavURL(input, match); + result_.Reset(); + + return url; +} + +AutocompleteLog* AutocompletePopupModel::GetAutocompleteLog() { + return new AutocompleteLog(input_.text(), selected_line_, 0, result_); +} + +void AutocompletePopupModel::Move(int count) { + // The user is using the keyboard to change the selection, so stop tracking + // hover. + SetHoveredLine(kNoMatch); + + // Force the popup to open/update, so the user is interacting with the + // latest results. + CommitLatestResults(false); + if (result_.empty()) + return; + + // Clamp the new line to [0, result_.count() - 1]. + const size_t new_line = selected_line_ + count; + SetSelectedLine(((count < 0) && (new_line >= selected_line_)) ? + 0 : std::min(new_line, result_.size() - 1)); +} + +void AutocompletePopupModel::TryDeletingCurrentItem() { + // We could use URLsForCurrentSelection() here, but it seems better to try + // and shift-delete the actual selection, rather than any "in progress, not + // yet visible" one. + if (selected_line_ == kNoMatch) + return; + const AutocompleteMatch& match = result_.match_at(selected_line_); + if (match.deletable) { + query_in_progress_ = true; + size_t selected_line = selected_line_; + match.provider->DeleteMatch(match); // This will synchronously call back + // to OnAutocompleteUpdate() + CommitLatestResults(false); + if (!result_.empty()) { + // Move the selection to the next choice after the deleted one, but clear + // the manual selection so this choice doesn't act "sticky". + // + // It might also be correct to only call Clear() here when + // manually_selected_match_ didn't already have a provider() (i.e. when + // there was no existing manual selection). It's not clear what the user + // wants when they shift-delete something they've arrowed to. If they + // arrowed down just to shift-delete it, we should probably Clear(); if + // they arrowed to do something else, then got a bad match while typing, + // we probably shouldn't. + SetSelectedLine(std::min(result_.size() - 1, selected_line)); + manually_selected_match_.Clear(); + } + } +} + +void AutocompletePopupModel::OpenLine(size_t line, + WindowOpenDisposition disposition) { + const AutocompleteMatch& match = result_.match_at(line); + // OpenURL() may close the popup, which will clear the result set and, by + // extension, |match| and its contents. So copy the relevant strings out to + // make sure they stay alive until the call completes. + const std::wstring url(match.destination_url); + std::wstring keyword; + const bool is_keyword_hint = GetKeywordForMatch(match, &keyword); + editor_->OpenURL(url, disposition, match.transition, std::wstring(), line, + is_keyword_hint ? std::wstring() : keyword); +} + +void AutocompletePopupModel::OnAutocompleteUpdate(bool updated_result, + bool query_complete) { + DCHECK(query_in_progress_); + if (updated_result) + controller_->GetResult(&latest_result_); + query_in_progress_ = !query_complete; + + SetDefaultMatchAndUpdate(query_complete); +} + +void AutocompletePopupModel::Run() { + CommitLatestResults(false); +} + +void AutocompletePopupModel::SetDefaultMatchAndUpdate(bool immediately) { + if (!latest_result_.SetDefaultMatch(manually_selected_match_) && + !query_in_progress_) { + // We don't clear the provider affinity because the user didn't do + // something to indicate that they want a different provider, we just + // couldn't find the specific match they wanted. + manually_selected_match_.destination_url.clear(); + manually_selected_match_.is_history_what_you_typed_match = false; + } + + if (immediately) { + CommitLatestResults(true); + } else if (!update_pending_) { + // Coalesce the results for the next kPopupCoalesceMs milliseconds. + update_pending_ = true; + MessageLoop::current()->timer_manager()->ResetTimer(coalesce_timer_.get()); + } + + // Update the edit with the possibly new data for this match. + // NOTE: This must be done after the code above, so that our internal state + // will be consistent when the edit calls back to URLsForCurrentSelection(). + std::wstring inline_autocomplete_text; + std::wstring keyword; + bool is_keyword_hint = false; + bool can_show_search_hint = true; + const AutocompleteResult::const_iterator match( + latest_result_.default_match()); + if (match != latest_result_.end()) { + if ((match->inline_autocomplete_offset != std::wstring::npos) && + (match->inline_autocomplete_offset < match->fill_into_edit.length())) { + inline_autocomplete_text = + match->fill_into_edit.substr(match->inline_autocomplete_offset); + } + // Warm up DNS Prefetch Cache. + chrome_browser_net::DnsPrefetchUrlString(match->destination_url); + // We could prefetch the alternate nav URL, if any, but because there can be + // many of these as a user types an initial series of characters, the OS DNS + // cache could suffer eviction problems for minimal gain. + + is_keyword_hint = GetKeywordForMatch(*match, &keyword); + can_show_search_hint = (match->type == AutocompleteMatch::SEARCH); + } + editor_->OnPopupDataChanged(inline_autocomplete_text, false, + manually_selected_match_ /* ignored */, keyword, is_keyword_hint, + can_show_search_hint); +} + +void AutocompletePopupModel::CommitLatestResults(bool force) { + if (!force && !update_pending_) + return; + + update_pending_ = false; + + // If we're going to trim the window size to no longer include the hovered + // line, turn hover off first. We need to do this before changing result_ + // since SetHoveredLine() should be able to trust that the old and new hovered + // lines are valid. + // + // Practically, this only currently happens when we're closing the window by + // setting latest_result_ to an empty list. + if ((hovered_line_ != kNoMatch) && (latest_result_.size() <= hovered_line_)) + SetHoveredLine(kNoMatch); + + result_.CopyFrom(latest_result_); + selected_line_ = (result_.default_match() == result_.end()) ? + kNoMatch : (result_.default_match() - result_.begin()); + + view_->UpdatePopupAppearance(); + + // The max update interval timer either needs to be reset (if more updates + // are to come) or stopped (when we're done with the query). The coalesce + // timer should always just be stopped. + TimerManager* const tm = MessageLoop::current()->timer_manager(); + tm->StopTimer(coalesce_timer_.get()); + if (query_in_progress_) + tm->ResetTimer(max_delay_timer_.get()); + else + tm->StopTimer(max_delay_timer_.get()); +} + +bool AutocompletePopupModel::GetKeywordForMatch(const AutocompleteMatch& match, + std::wstring* keyword) { + // Assume we have no keyword until we find otherwise. + keyword->clear(); + + // If the current match is a keyword, return that as the selected keyword. + if (match.template_url && match.template_url->url() && + match.template_url->url()->SupportsReplacement()) { + keyword->assign(match.template_url->keyword()); + return false; + } + + // See if the current match's fill_into_edit corresponds to a keyword. + if (!profile_->GetTemplateURLModel()) + return false; + profile_->GetTemplateURLModel()->Load(); + const std::wstring keyword_hint( + TemplateURLModel::CleanUserInputKeyword(match.fill_into_edit)); + if (keyword_hint.empty()) + return false; + + // Don't provide a hint if this keyword doesn't support replacement. + const TemplateURL* const template_url = + profile_->GetTemplateURLModel()->GetTemplateURLForKeyword(keyword_hint); + if (!template_url || !template_url->url() || + !template_url->url()->SupportsReplacement()) + return false; + + keyword->assign(keyword_hint); + return true; +} diff --git a/chrome/browser/autocomplete/autocomplete_popup.h b/chrome/browser/autocomplete/autocomplete_popup.h index 556ea49..395f774 100644 --- a/chrome/browser/autocomplete/autocomplete_popup.h +++ b/chrome/browser/autocomplete/autocomplete_popup.h @@ -17,25 +17,28 @@ #include "chrome/common/gfx/chrome_font.h" #include "chrome/views/view.h" -#define AUTOCOMPLETEPOPUP_CLASSNAME L"Chrome_AutocompletePopup" - class AutocompleteEdit; +class AutocompletePopupModel; +class AutocompletePopupView; class Profile; -class SkBitmap; class MirroringContext; +class SkBitmap; + +// TODO(pkasting): http://b/1343512 The names and contents of the classes in +// this file are temporary. I am in hack-and-slash mode right now. + +#define AUTOCOMPLETEPOPUPVIEW_CLASSNAME L"Chrome_AutocompletePopupView" // This class implements a popup window used by AutocompleteEdit to display // autocomplete results. -class AutocompletePopup - : public CWindowImpl<AutocompletePopup, CWindow, CControlWinTraits>, - public ACControllerListener, - public Task { +class AutocompletePopupView + : public CWindowImpl<AutocompletePopupView, CWindow, CControlWinTraits> { public: - DECLARE_WND_CLASS_EX(AUTOCOMPLETEPOPUP_CLASSNAME, + DECLARE_WND_CLASS_EX(AUTOCOMPLETEPOPUPVIEW_CLASSNAME, ((win_util::GetWinVersion() < win_util::WINVERSION_XP) ? 0 : CS_DROPSHADOW), COLOR_WINDOW) - BEGIN_MSG_MAP(AutocompletePopup) + BEGIN_MSG_MAP(AutocompletePopupView) MSG_WM_ERASEBKGND(OnEraseBkgnd) MSG_WM_LBUTTONDOWN(OnLButtonDown) MSG_WM_MBUTTONDOWN(OnMButtonDown) @@ -47,114 +50,20 @@ class AutocompletePopup MSG_WM_PAINT(OnPaint) END_MSG_MAP() - AutocompletePopup(const ChromeFont& font, - AutocompleteEdit* editor, - Profile* profile); - ~AutocompletePopup(); - - // Invoked when the profile has changed. - void SetProfile(Profile* profile); - - // Gets autocomplete results for the given text. If there are results, opens - // the popup if necessary and fills it with the new data. Otherwise, closes - // the popup if necessary. - // - // |prevent_inline_autocomplete| is true if the generated result set should - // not require inline autocomplete for the default match. This is difficult - // to explain in the abstract; the practical use case is that after the user - // deletes text in the edit, the HistoryURLProvider should make sure not to - //promote a match requiring inline autocomplete too highly. - void StartAutocomplete(const std::wstring& text, - const std::wstring& desired_tld, - bool prevent_inline_autocomplete); - - // Closes the window and cancels any pending asynchronous queries - void StopAutocomplete(); - - // Returns true if no autocomplete query is currently running. - bool query_in_progress() const { return query_in_progress_; } + AutocompletePopupView(AutocompletePopupModel* model, const ChromeFont& font); // Returns true if the popup is currently open. bool is_open() const { return m_hWnd != NULL; } - // Returns the URL for the selected match. If an update is in progress, - // "selected" means "default in the latest results". If there are no - // results, returns the empty string. - // - // If |transition_type| is non-NULL, it will be set to the appropriate - // transition type for the selected entry (TYPED or GENERATED). - // - // If |is_history_what_you_typed_match| is non-NULL, it will be set based on - // the selected entry's is_history_what_you_typed value. - // - // If |alternate_nav_url| is non-NULL, it will be set to the alternate - // navigation URL for |url| if one exists, or left unchanged otherwise. See - // comments on AutocompleteResult::GetAlternateNavURL(). - std::wstring URLsForCurrentSelection( - PageTransition::Type* transition, - bool* is_history_what_you_typed_match, - std::wstring* alternate_nav_url) const; - - // This is sort of a hybrid between StartAutocomplete() and - // URLForCurrentSelection(). When the popup isn't open and the user hits - // enter, we want to get the default result for the user's input immediately, - // and not open the popup, continue running autocomplete, etc. Therefore, - // this does a query for only the synchronously available results for the - // provided input parameters, sets |transition|, - // |is_history_what_you_typed_match|, and |alternate_nav_url| (if applicable) - // based on the default match, and returns its url. |transition|, - // |is_history_what_you_typed_match| and/or |alternate_nav_url| may be null, - // in which case they are not updated. - // - // If there are no matches for |text|, leaves the outparams unset and returns - // the empty string. - std::wstring URLsForDefaultMatch(const std::wstring& text, - const std::wstring& desired_tld, - PageTransition::Type* transition, - bool* is_history_what_you_typed_match, - std::wstring* alternate_nav_url); - - // Returns a pointer to a heap-allocated AutocompleteLog containing the - // current input text, selected match, and result set. The caller is - // responsible for deleting the object. - AutocompleteLog* GetAutocompleteLog(); - - // Immediately updates and opens the popup if necessary, then moves the - // current selection down (|count| > 0) or up (|count| < 0), clamping to the - // first or last result if necessary. If |count| == 0, the selection will be - // unchanged, but the popup will still redraw and modify the text in the - // AutocompleteEdit. - void Move(int count); - - // Called when the user hits shift-delete. This should determine if the item - // can be removed from history, and if so, remove it and update the popup. - void TryDeletingCurrentItem(); - - // ACControllerListener - called when more autocomplete data is available or - // when the query is complete. - // - // When the input for the current query has a provider affinity, we try to - // keep the current result set's default match as the new default match. - virtual void OnAutocompleteUpdate(bool updated_result, bool query_complete); - - // Task - called when either timer fires. Calls CommitLatestResults(). - virtual void Run(); - - // Returns the AutocompleteController used by this popup. - AutocompleteController* autocomplete_controller() const { - return controller_.get(); - } - - const AutocompleteResult* latest_result() const { - return &latest_result_; - } + // Invalidates one line of the autocomplete popup. + void InvalidateLine(size_t line); - // The match the user has manually chosen, if any. - AutocompleteResult::Selection manually_selected_match_; + // Redraws the popup window to match any changes in result_; this may mean + // opening or closing the window. + void UpdatePopupAppearance(); - // The token value for selected_line_, hover_line_ and functions dealing with - // a "line number" that indicates "no line". - static const size_t kNoMatch = -1; + // Called by the model when hover is enabled or disabled. + void OnHoverEnabledOrDisabled(bool disabled); private: // Caches GDI objects and information for drawing. @@ -218,20 +127,6 @@ class AutocompletePopup // disposition. void OnButtonUp(const CPoint& point, WindowOpenDisposition disposition); - // Sets the correct default match in latest_result_, then updates the popup - // appearance to match. If |immediately| is true this update happens - // synchronously; otherwise, it's deferred until the next scheduled update. - void SetDefaultMatchAndUpdate(bool immediately); - - // If an update is pending or |force| is true, immediately updates result_ to - // match latest_result_, and calls UpdatePopup() to reflect those changes - // back to the user. - void CommitLatestResults(bool force); - - // Redraws the popup window to match any changes in result_; this may mean - // opening or closing the window. - void UpdatePopupAppearance(); - // Gives the topmost y coordinate within |line|, which should be within the // range of valid lines. int LineTopPixel(size_t line) const; @@ -243,58 +138,13 @@ class AutocompletePopup // bottom edges of the window, respectively. size_t PixelToLine(int y) const; - // Call to change the hovered line. |line| should be within the range of - // valid lines (to enable hover) or kNoMatch (to disable hover). - void SetHoveredLine(size_t line); - - // Call to change the selected line. This will update all state and repaint - // the necessary parts of the window, as well as updating the edit with the - // new temporary text. |line| should be within the range of valid lines. - // NOTE: This assumes the popup is open, and thus both old and new values for - // the selected line should not be kNoMatch. - void SetSelectedLine(size_t line); - - // Invalidates one line of the autocomplete popup. - void InvalidateLine(size_t line); - // Draws a light border around the inside of the window with the given client // rectangle and DC. void DrawBorder(const RECT& rc, HDC dc); - // Draw a string at the specified location with the specified style. - // This function is a wrapper function of the DrawText() function to handle - // bidirectional strings. - // Parameters - // * dc [in] (HDC) - // Represents the display context to render the given string. - // * x [in] (int) - // Specifies the left of the bounding rectangle, - // * y [in] (int) - // Specifies the top of the bounding rectangle, - // * max_x [in] (int) - // Specifies the right of the bounding rectangle. - // * text [in] (const wchar_t*) - // Specifies the pointer to the string to be rendered. - // * length [in] (int) - // Specifies the number of characters in the string. - // * style [in] (int) - // Specifies the classifications for this string. - // This value is a combination of the following values: - // - ACMatchClassifications::NONE - // - ACMatchClassifications::URL - // - ACMatchClassifications::MATCH - // - ACMatchClassifications::DIM - // * status [in] (const DrawLineInfo::LineStatus) - // Specifies the classifications for this line. - // * context [in] (const MirroringContext*) - // Specifies the context used for mirroring the x-coordinates. - // * text_direction_is_rtl [in] (bool) - // Determines whether we need to render the string as an RTL string, which - // impacts, for example, which side leading/trailing whitespace and - // punctuation appear on. - // Return Values - // * a positive value - // Represents the width of the displayed string, in pixels. + // Draws a single run of text with a particular style. Handles both LTR and + // RTL text as well as eliding. Returns the width, in pixels, of the string + // as it was actually displayed. int DrawString(HDC dc, int x, int y, @@ -327,6 +177,176 @@ class AutocompletePopup // Draws the star at the specified location void DrawStar(HDC dc, int x, int y) const; + AutocompletePopupModel* model_; + + // Cached GDI information for drawing. + DrawLineInfo line_info_; + + // Bitmap for the star. This is owned by the ResourceBundle. + SkBitmap* star_; + + // A context used for mirroring regions. + scoped_ptr<MirroringContext> mirroring_context_; + + // When hovered_line_ is kNoMatch, this holds the screen coordinates of the + // mouse position when hover tracking was turned off. If the mouse moves to a + // point over the popup that has different coordinates, hover tracking will be + // re-enabled. When hovered_line_ is a valid line, the value here is + // out-of-date and should be ignored. + CPoint last_hover_coordinates_; +}; + +class AutocompletePopupModel : public ACControllerListener, public Task { + public: + AutocompletePopupModel(const ChromeFont& font, + AutocompleteEdit* editor, + Profile* profile); + ~AutocompletePopupModel(); + + // Invoked when the profile has changed. + void SetProfile(Profile* profile); + + // Gets autocomplete results for the given text. If there are results, opens + // the popup if necessary and fills it with the new data. Otherwise, closes + // the popup if necessary. + // + // |prevent_inline_autocomplete| is true if the generated result set should + // not require inline autocomplete for the default match. This is difficult + // to explain in the abstract; the practical use case is that after the user + // deletes text in the edit, the HistoryURLProvider should make sure not to + //promote a match requiring inline autocomplete too highly. + void StartAutocomplete(const std::wstring& text, + const std::wstring& desired_tld, + bool prevent_inline_autocomplete); + + // Closes the window and cancels any pending asynchronous queries + void StopAutocomplete(); + + // Returns true if the popup is currently open. + bool is_open() const { return view_->is_open(); } + + // TODO(pkasting): http://b/134593 This is temporary and should die. + const AutocompleteEdit* editor() const { return editor_; } + + // Returns the AutocompleteController used by this popup. + AutocompleteController* autocomplete_controller() const { + return controller_.get(); + } + + // Returns true if no autocomplete query is currently running. + bool query_in_progress() const { return query_in_progress_; } + + const AutocompleteResult* result() const { + return &result_; + } + + const AutocompleteResult* latest_result() const { + return &latest_result_; + } + + size_t hovered_line() const { + return hovered_line_; + } + + // Call to change the hovered line. |line| should be within the range of + // valid lines (to enable hover) or kNoMatch (to disable hover). + void SetHoveredLine(size_t line); + + size_t selected_line() const { + return selected_line_; + } + + // Call to change the selected line. This will update all state and repaint + // the necessary parts of the window, as well as updating the edit with the + // new temporary text. |line| should be within the range of valid lines. + // NOTE: This assumes the popup is open, and thus both old and new values for + // the selected line should not be kNoMatch. + void SetSelectedLine(size_t line); + + // Returns the URL for the selected match. If an update is in progress, + // "selected" means "default in the latest results". If there are no + // results, returns the empty string. + // + // If |transition_type| is non-NULL, it will be set to the appropriate + // transition type for the selected entry (TYPED or GENERATED). + // + // If |is_history_what_you_typed_match| is non-NULL, it will be set based on + // the selected entry's is_history_what_you_typed value. + // + // If |alternate_nav_url| is non-NULL, it will be set to the alternate + // navigation URL for |url| if one exists, or left unchanged otherwise. See + // comments on AutocompleteResult::GetAlternateNavURL(). + std::wstring URLsForCurrentSelection( + PageTransition::Type* transition, + bool* is_history_what_you_typed_match, + std::wstring* alternate_nav_url) const; + + // This is sort of a hybrid between StartAutocomplete() and + // URLForCurrentSelection(). When the popup isn't open and the user hits + // enter, we want to get the default result for the user's input immediately, + // and not open the popup, continue running autocomplete, etc. Therefore, + // this does a query for only the synchronously available results for the + // provided input parameters, sets |transition|, + // |is_history_what_you_typed_match|, and |alternate_nav_url| (if applicable) + // based on the default match, and returns its url. |transition|, + // |is_history_what_you_typed_match| and/or |alternate_nav_url| may be null, + // in which case they are not updated. + // + // If there are no matches for |text|, leaves the outparams unset and returns + // the empty string. + std::wstring URLsForDefaultMatch(const std::wstring& text, + const std::wstring& desired_tld, + PageTransition::Type* transition, + bool* is_history_what_you_typed_match, + std::wstring* alternate_nav_url); + + // Returns a pointer to a heap-allocated AutocompleteLog containing the + // current input text, selected match, and result set. The caller is + // responsible for deleting the object. + AutocompleteLog* GetAutocompleteLog(); + + // Immediately updates and opens the popup if necessary, then moves the + // current selection down (|count| > 0) or up (|count| < 0), clamping to the + // first or last result if necessary. If |count| == 0, the selection will be + // unchanged, but the popup will still redraw and modify the text in the + // AutocompleteEdit. + void Move(int count); + + // Called when the user hits shift-delete. This should determine if the item + // can be removed from history, and if so, remove it and update the popup. + void TryDeletingCurrentItem(); + + // Called by the view to open the URL corresponding to a particular line. + void OpenLine(size_t line, WindowOpenDisposition disposition); + + // ACControllerListener - called when more autocomplete data is available or + // when the query is complete. + // + // When the input for the current query has a provider affinity, we try to + // keep the current result set's default match as the new default match. + virtual void OnAutocompleteUpdate(bool updated_result, bool query_complete); + + // Task - called when either timer fires. Calls CommitLatestResults(). + virtual void Run(); + + // The match the user has manually chosen, if any. + AutocompleteResult::Selection manually_selected_match_; + + // The token value for selected_line_, hover_line_ and functions dealing with + // a "line number" that indicates "no line". + static const size_t kNoMatch = -1; + + private: + // Sets the correct default match in latest_result_, then updates the popup + // appearance to match. If |immediately| is true this update happens + // synchronously; otherwise, it's deferred until the next scheduled update. + void SetDefaultMatchAndUpdate(bool immediately); + + // If an update is pending or |force| is true, immediately updates result_ to + // match latest_result_, and calls UpdatePopupAppearance() to reflect those + // changes back to the user. + void CommitLatestResults(bool force); + // Gets the selected keyword or keyword hint for the given match. Returns // true if |keyword| represents a keyword hint, or false if |keyword| // represents a selected keyword. (|keyword| will always be set [though @@ -335,15 +355,14 @@ class AutocompletePopup bool GetKeywordForMatch(const AutocompleteMatch& match, std::wstring* keyword); + scoped_ptr<AutocompletePopupView> view_; + AutocompleteEdit* editor_; scoped_ptr<AutocompleteController> controller_; // Profile for current tab. Profile* profile_; - // Cached GDI information for drawing. - DrawLineInfo line_info_; - // The input for the current query. AutocompleteInput input_; @@ -380,22 +399,9 @@ class AutocompletePopup // this will be kNoMatch, even if the cursor is over the popup contents. size_t hovered_line_; - // When hover_line_ is kNoMatch, this holds the screen coordinates of the - // mouse position when hover tracking was turned off. If the mouse moves to a - // point over the popup that has different coordinates, hover tracking will be - // re-enabled. When hover_line_ is a valid line, the value here is - // out-of-date and should be ignored. - CPoint last_hover_coordinates_; - // The currently selected line. This is kNoMatch when nothing is selected, // which should only be true when the popup is closed. size_t selected_line_; - - // Bitmap for the star. This is owned by the ResourceBundle. - SkBitmap* star_; - - // A context used for mirroring regions. - scoped_ptr<MirroringContext> mirroring_context_; }; #endif // CHROME_BROWSER_AUTOCOMPLETE_AUTOCOMPLETE_POPUP_H_ |