diff options
| author | deanm@chromium.org <deanm@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2009-02-27 14:49:51 +0000 |
|---|---|---|
| committer | deanm@chromium.org <deanm@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2009-02-27 14:49:51 +0000 |
| commit | f387f1b80dc74fd2d77820e23b885e7984455763 (patch) | |
| tree | 1f3ee3729aec8dc4468adcd1de54298f9e1586b5 /chrome/browser/autocomplete/autocomplete_popup_view_win.cc | |
| parent | 5e689362e7c6f7b2535aa5ae6dbb198e03a372fb (diff) | |
| download | chromium_src-f387f1b80dc74fd2d77820e23b885e7984455763.zip chromium_src-f387f1b80dc74fd2d77820e23b885e7984455763.tar.gz chromium_src-f387f1b80dc74fd2d77820e23b885e7984455763.tar.bz2 | |
Pull the autocomplete popup into separate model and view files.
- autocomplete_popup.h is now autocomplete_popup_{model,view}.h.
- autocomplete_popup.cc is now autocomplete_popup_{model,view_win}.cc
- The view header is still Windows specific, but this will be addressed soon.
- Rename is_open to IsOpen, in preparation for making a interface for the view.
- Update the project files.
Review URL: http://codereview.chromium.org/27272
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@10606 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'chrome/browser/autocomplete/autocomplete_popup_view_win.cc')
| -rwxr-xr-x | chrome/browser/autocomplete/autocomplete_popup_view_win.cc | 664 |
1 files changed, 664 insertions, 0 deletions
diff --git a/chrome/browser/autocomplete/autocomplete_popup_view_win.cc b/chrome/browser/autocomplete/autocomplete_popup_view_win.cc new file mode 100755 index 0000000..dd122ee --- /dev/null +++ b/chrome/browser/autocomplete/autocomplete_popup_view_win.cc @@ -0,0 +1,664 @@ +// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "chrome/browser/autocomplete/autocomplete_popup_view.h" + +// TODO(deanm): Clean up these includes, not going to fight it now. +#include <atlbase.h> +#include <atlapp.h> +#include <atlcrack.h> +#include <atlmisc.h> +#include <cmath> + +#include "base/scoped_ptr.h" +#include "base/string_util.h" +#include "base/win_util.h" +#include "chrome/browser/autocomplete/autocomplete.h" +#include "chrome/browser/autocomplete/autocomplete_edit.h" +#include "chrome/browser/autocomplete/autocomplete_popup_model.h" +#include "chrome/browser/browser_process.h" +#include "chrome/browser/net/dns_global.h" +#include "chrome/browser/profile.h" +#include "chrome/browser/search_engines/template_url.h" +#include "chrome/browser/search_engines/template_url_model.h" +#include "chrome/browser/views/location_bar_view.h" +#include "chrome/common/gfx/chrome_canvas.h" +#include "chrome/common/gfx/chrome_font.h" +#include "chrome/common/l10n_util.h" +#include "chrome/common/notification_service.h" +#include "chrome/common/resource_bundle.h" +#include "chrome/views/view.h" +#include "grit/theme_resources.h" +#include "third_party/icu38/public/common/unicode/ubidi.h" + +namespace { + +// Padding between text and the star indicator, in pixels. +const int kStarPadding = 4; + +} // namespace + +// This class implements a utility used for mirroring x-coordinates when the +// application language is a right-to-left one. +class MirroringContext { + public: + 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 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 + // calculates the mirrored coordinates of the input region and returns the + // left side of the mirrored region. + // The input region must be in the bounding region specified in the + // Initialize() function. + int GetLeft(int x1, int x2) const; + + // Returns whether or not we are mirroring the x coordinate. + bool enabled() const { + return enabled_; + } + + private: + int min_x_; + int center_x_; + int max_x_; + bool enabled_; + + DISALLOW_EVIL_CONSTRUCTORS(MirroringContext); +}; + +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 x1, int x2) const { + return enabled_ ? + (center_x_ + (center_x_ - std::max(x1, x2))) : std::min(x1, x2); +} + +const wchar_t AutocompletePopupView::DrawLineInfo::ellipsis_str[] = L"\x2026"; + +AutocompletePopupView::AutocompletePopupView(AutocompletePopupModel* model, + const ChromeFont& font, + AutocompleteEditView* edit_view) + : model_(model), + edit_view_(edit_view), + line_info_(font), + mirroring_context_(new MirroringContext()), + star_(ResourceBundle::GetSharedInstance().GetBitmapNamed( + IDR_CONTENT_STAR_ON)) { +} + +void AutocompletePopupView::InvalidateLine(size_t line) { + RECT rc; + GetClientRect(&rc); + rc.top = LineTopPixel(line); + rc.bottom = rc.top + line_info_.line_height; + InvalidateRect(&rc, false); +} + +void AutocompletePopupView::UpdatePopupAppearance() { + const AutocompleteResult& result = model_->result(); + if (result.empty()) { + // No matches, 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. + // TODO(pkasting): http://b/1345937 All this use of editor accessors should + // die once this class is a true ChromeView. + CRect rc = edit_view_->parent_view()->bounds().ToRECT(); + // Subtract the top left corner to make the coordinates relative to the + // location bar view itself, and convert to screen coordinates. + gfx::Point top_left(-rc.TopLeft()); + views::View::ConvertPointToScreen(edit_view_->parent_view(), &top_left); + rc.OffsetRect(top_left.ToPOINT()); + // 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::kVertMargin); + // 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(edit_view_->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(edit_view_->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 edit_view_'s IME context here, and exclude ourselves from its display + // area. Not clear what to pass for the lpCandidate->ptCurrentPos member, + // though... +} + +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 + + // 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 AutocompletePopupView::OnLButtonDown(UINT keys, const CPoint& point) { + const size_t new_hovered_line = PixelToLine(point.y); + model_->SetHoveredLine(new_hovered_line); + model_->SetSelectedLine(new_hovered_line, false); +} + +void AutocompletePopupView::OnMButtonDown(UINT keys, const CPoint& point) { + model_->SetHoveredLine(PixelToLine(point.y)); +} + +void AutocompletePopupView::OnLButtonUp(UINT keys, const CPoint& point) { + OnButtonUp(point, CURRENT_TAB); +} + +void AutocompletePopupView::OnMButtonUp(UINT keys, const CPoint& point) { + OnButtonUp(point, NEW_BACKGROUND_TAB); +} + +LRESULT AutocompletePopupView::OnMouseActivate(HWND window, + UINT hit_test, + UINT mouse_message) { + return MA_NOACTIVATE; +} + +void AutocompletePopupView::OnMouseLeave() { + // The mouse has left the window, so no line is hovered. + model_->SetHoveredLine(AutocompletePopupModel::kNoMatch); +} + +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) + // (b) The user moves the mouse from where we last stopped tracking hover + // (c) We started tracking previously due to (a) or (b) and haven't stopped + // yet (user hasn't used the keyboard to interact again) + const bool action_button_pressed = !!(keys & (MK_MBUTTON | MK_LBUTTON)); + CPoint screen_point(point); + ClientToScreen(&screen_point); + if (action_button_pressed || (last_hover_coordinates_ != screen_point) || + (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); + 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) + model_->SetSelectedLine(new_hovered_line, false); + } +} + +void AutocompletePopupView::OnPaint(HDC other_dc) { + const AutocompleteResult& result = model_->result(); + DCHECK(!result.empty()); // Shouldn't be drawing an empty popup. + + CPaintDC dc(m_hWnd); + + RECT rc; + GetClientRect(&rc); + mirroring_context_->Initialize(rc.left, rc.right, + l10n_util::GetTextDirection() == l10n_util::RIGHT_TO_LEFT); + DrawBorder(rc, dc); + + bool all_descriptions_empty = true; + for (AutocompleteResult::const_iterator i(result.begin()); i != result.end(); + ++i) { + if (!i->description.empty()) { + all_descriptions_empty = false; + break; + } + } + + // Only repaint the invalid lines. + const size_t first_line = PixelToLine(dc.m_ps.rcPaint.top); + const size_t last_line = PixelToLine(dc.m_ps.rcPaint.bottom); + for (size_t i = first_line; i <= last_line; ++i) { + DrawLineInfo::LineStatus status; + // Selection should take precedence over hover. + if (i == model_->selected_line()) + status = DrawLineInfo::SELECTED; + 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 AutocompletePopupView::OnButtonUp(const CPoint& point, + WindowOpenDisposition disposition) { + const size_t line = PixelToLine(point.y); + const AutocompleteMatch& match = model_->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 GURL url(match.destination_url); + std::wstring keyword; + const bool is_keyword_hint = model_->GetKeywordForMatch(match, &keyword); + edit_view_->OpenURL(url, disposition, match.transition, GURL(), line, + is_keyword_hint ? std::wstring() : keyword); +} + +int AutocompletePopupView::LineTopPixel(size_t line) const { + // The popup has a 1 px top border. + return line_info_.line_height * static_cast<int>(line) + 1; +} + +size_t AutocompletePopupView::PixelToLine(int y) const { + const size_t line = std::max(y - 1, 0) / line_info_.line_height; + 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 AutocompletePopupView::DrawBorder(const RECT& rc, HDC dc) { + HPEN hpen = CreatePen(PS_SOLID, 1, RGB(199, 202, 206)); + HGDIOBJ old_pen = SelectObject(dc, hpen); + + int width = rc.right - rc.left - 1; + int height = rc.bottom - rc.top - 1; + + MoveToEx(dc, 0, 0, NULL); + LineTo(dc, 0, height); + LineTo(dc, width, height); + LineTo(dc, width, 0); + LineTo(dc, 0, 0); + + SelectObject(dc, old_pen); + DeleteObject(hpen); +} + +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; + + // Set up the text decorations. + SelectObject(dc, (style & ACMatchClassification::MATCH) ? + line_info_.bold_font.hfont() : line_info_.regular_font.hfont()); + const COLORREF foreground = (style & ACMatchClassification::URL) ? + line_info_.url_colors[status] : line_info_.text_colors[status]; + const COLORREF background = line_info_.background_colors[status]; + SetTextColor(dc, (style & ACMatchClassification::DIM) ? + DrawLineInfo::AlphaBlend(foreground, background, 0xAA) : foreground); + + // Retrieve the width of the decorated text and display it. When we cannot + // display this fragment in the given width, we trim the fragment and add an + // ellipsis. + // + // TODO(hbono): http:///b/1222425 We should change the following eliding code + // with more aggressive one. + int text_x = x; + int max_length = 0; + SIZE text_size = {0}; + GetTextExtentExPoint(dc, text, length, + max_x - line_info_.ellipsis_width - text_x, &max_length, + NULL, &text_size); + + if (max_length < length) + GetTextExtentPoint32(dc, text, max_length, &text_size); + + const int mirrored_x = context->GetLeft(text_x, text_x + text_size.cx); + RECT text_bounds = {mirrored_x, + 0, + mirrored_x + text_size.cx, + line_info_.line_height}; + + int flags = DT_SINGLELINE | DT_NOPREFIX; + if (text_direction_is_rtl) + // In order to make sure RTL text is displayed correctly (for example, a + // trailing space should be displayed on the left and not on the right), we + // pass the flag DT_RTLREADING. + flags |= DT_RTLREADING; + + DrawText(dc, text, length, &text_bounds, flags); + text_x += text_size.cx; + + // Draw the ellipsis. Note that since we use the mirroring context, the + // ellipsis are drawn either to the right or to the left of the text. + if (max_length < length) { + TextOut(dc, context->GetLeft(text_x, text_x + line_info_.ellipsis_width), + 0, line_info_.ellipsis_str, arraysize(line_info_.ellipsis_str) - 1); + text_x += line_info_.ellipsis_width; + } + + return text_x - x; +} + +void AutocompletePopupView::DrawMatchFragments( + HDC dc, + const std::wstring& text, + const ACMatchClassifications& classifications, + int x, + int y, + int max_x, + DrawLineInfo::LineStatus status) const { + if (!text.length()) + return; + + // Check whether or not this text is a URL string. + // A URL string is basically in English with possible included words in + // Arabic or Hebrew. For such case, ICU provides a special algorithm and we + // should use it. + bool url = false; + for (ACMatchClassifications::const_iterator i = classifications.begin(); + i != classifications.end(); ++i) { + if (i->style & ACMatchClassification::URL) + url = true; + } + + // Initialize a bidirectional line iterator of ICU and split the text into + // visual runs. (A visual run is consecutive characters which have the same + // display direction and should be displayed at once.) + l10n_util::BiDiLineIterator bidi_line; + if (!bidi_line.Open(text, mirroring_context_->enabled(), url)) + return; + const int runs = bidi_line.CountRuns(); + + // Draw the visual runs. + // This loop splits each run into text fragments with the given + // classifications and draws the text fragments. + // When the direction of a run is right-to-left, we have to mirror the + // x-coordinate of this run and render the fragments in the right-to-left + // reading order. To handle this display order independently from the one of + // this popup window, this loop renders a run with the steps below: + // 1. Create a local display context for each run; + // 2. Render the run into the local display context, and; + // 3. Copy the local display context to the one of the popup window. + int run_x = x; + for (int run = 0; run < runs; ++run) { + int run_start = 0; + int run_length = 0; + + // The index we pass to GetVisualRun corresponds to the position of the run + // in the displayed text. For example, the string "Google in HEBREW" (where + // HEBREW is text in the Hebrew language) has two runs: "Google in " which + // is an LTR run, and "HEBREW" which is an RTL run. In an LTR context, the + // run "Google in " has the index 0 (since it is the leftmost run + // displayed). In an RTL context, the same run has the index 1 because it + // is the rightmost run. This is why the order in which we traverse the + // runs is different depending on the locale direction. + // + // Note that for URLs we always traverse the runs from lower to higher + // indexes because the return order of runs for a URL always matches the + // physical order of the context. + int current_run = + (mirroring_context_->enabled() && !url) ? (runs - run - 1) : run; + const UBiDiDirection run_direction = bidi_line.GetVisualRun(current_run, + &run_start, + &run_length); + const int run_end = run_start + run_length; + + // Set up a local display context for rendering this run. + int text_x = 0; + const int text_max_x = max_x - run_x; + MirroringContext run_context; + run_context.Initialize(0, text_max_x, run_direction == UBIDI_RTL); + + // In addition to creating a mirroring context for the run, we indicate + // whether the run needs to be rendered as RTL text. The mirroring context + // alone in not sufficient because there are cases where a mirrored RTL run + // needs to be rendered in an LTR context (for example, an RTL run within + // an URL). + bool run_direction_is_rtl = (run_direction == UBIDI_RTL) && !url; + CDC text_dc(CreateCompatibleDC(dc)); + CBitmap text_bitmap(CreateCompatibleBitmap(dc, text_max_x, + line_info_.font_height)); + SelectObject(text_dc, text_bitmap); + const RECT text_rect = {0, 0, text_max_x, line_info_.line_height}; + FillRect(text_dc, &text_rect, line_info_.brushes[status]); + SetBkMode(text_dc, TRANSPARENT); + + // Split this run with the given classifications and draw the fragments + // into the local display context. + for (ACMatchClassifications::const_iterator i = classifications.begin(); + i != classifications.end(); ++i) { + const int text_start = std::max(run_start, static_cast<int>(i->offset)); + const int text_end = std::min(run_end, (i != classifications.end() - 1) ? + static_cast<int>((i + 1)->offset) : run_end); + text_x += DrawString(text_dc, text_x, 0, text_max_x, &text[text_start], + text_end - text_start, i->style, status, + &run_context, run_direction_is_rtl); + } + + // Copy the local display context to the one of the popup window and + // delete the local display context. + BitBlt(dc, mirroring_context_->GetLeft(run_x, run_x + text_x), y, text_x, + line_info_.line_height, text_dc, run_context.GetLeft(0, text_x), 0, + SRCCOPY); + run_x += text_x; + } +} + +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, + top_pixel + line_info_.line_height}; + FillRect(dc, &rc, line_info_.brushes[status]); + + // Calculate and display contents/description sections as follows: + // * 2 px top margin, bottom margin is handled by line_height. + const int y = rc.top + 2; + + // * 1 char left/right margin. + const int side_margin = line_info_.ave_char_width; + + // * 50% of the remaining width is initially allocated to each section, with + // a 2 char margin followed by the star column and kStarPadding padding. + const int content_min_x = rc.left + side_margin; + const int description_max_x = rc.right - side_margin; + const int mid_line = (description_max_x - content_min_x) / 2 + content_min_x; + const int star_col_width = kStarPadding + star_->width(); + const int content_right_margin = line_info_.ave_char_width * 2; + + // * If this would make the content section display fewer than 40 characters, + // the content section is increased to that minimum at the expense of the + // description section. + const int content_width = + std::max(mid_line - content_min_x - content_right_margin, + line_info_.ave_char_width * 40); + const int description_width = description_max_x - content_min_x - + content_width - star_col_width; + + // * If this would make the description section display fewer than 20 + // characters, or if there are no descriptions to display or the result is + // 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 = model_->result().match_at(line); + if ((description_width < (line_info_.ave_char_width * 20)) || + all_descriptions_empty || + (match.type == AutocompleteMatch::OPEN_HISTORY_PAGE)) { + star_x = description_max_x - star_col_width + kStarPadding; + DrawMatchFragments(dc, match.contents, match.contents_class, content_min_x, + y, star_x - kStarPadding, status); + } else { + star_x = description_max_x - description_width - star_col_width; + DrawMatchFragments(dc, match.contents, match.contents_class, content_min_x, + y, content_min_x + content_width, status); + DrawMatchFragments(dc, match.description, match.description_class, + description_max_x - description_width, y, + description_max_x, status); + } + if (starred) + DrawStar(dc, star_x, + (line_info_.line_height - star_->height()) / 2 + top_pixel); +} + +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); + canvas.DrawBitmapInt(*star_, 0, 0); + canvas.getTopPlatformDevice().drawToHDC( + dc, mirroring_context_->GetLeft(x, x + star_->width()), y, NULL); +} + +AutocompletePopupView::DrawLineInfo::DrawLineInfo(const ChromeFont& font) { + // Create regular and bold fonts. + regular_font = font.DeriveFont(-1); + bold_font = regular_font.DeriveFont(0, ChromeFont::BOLD); + + // The total padding added to each line (bottom padding is what is + // left over after DrawEntry() specifies its top offset). + static const int kTotalLinePadding = 5; + font_height = std::max(regular_font.height(), bold_font.height()); + line_height = font_height + kTotalLinePadding; + ave_char_width = regular_font.GetExpectedTextWidth(1); + ellipsis_width = std::max(regular_font.GetStringWidth(ellipsis_str), + bold_font.GetStringWidth(ellipsis_str)); + + // Create background colors. + background_colors[NORMAL] = GetSysColor(COLOR_WINDOW); + background_colors[SELECTED] = GetSysColor(COLOR_HIGHLIGHT); + background_colors[HOVERED] = + AlphaBlend(background_colors[SELECTED], background_colors[NORMAL], 0x40); + + // Create text colors. + text_colors[NORMAL] = GetSysColor(COLOR_WINDOWTEXT); + text_colors[HOVERED] = text_colors[NORMAL]; + text_colors[SELECTED] = GetSysColor(COLOR_HIGHLIGHTTEXT); + + // Create brushes and url colors. + const COLORREF dark_url(0x008000); + const COLORREF light_url(0xd0ffd0); + for (int i = 0; i < MAX_STATUS_ENTRIES; ++i) { + // Pick whichever URL color contrasts better. + const double dark_contrast = + LuminosityContrast(dark_url, background_colors[i]); + const double light_contrast = + LuminosityContrast(light_url, background_colors[i]); + url_colors[i] = (dark_contrast > light_contrast) ? dark_url : light_url; + + brushes[i] = CreateSolidBrush(background_colors[i]); + } +} + +AutocompletePopupView::DrawLineInfo::~DrawLineInfo() { + for (int i = 0; i < MAX_STATUS_ENTRIES; ++i) + DeleteObject(brushes[i]); +} + +// static +double AutocompletePopupView::DrawLineInfo::LuminosityContrast( + COLORREF color1, + COLORREF color2) { + // This algorithm was adapted from the following text at + // http://juicystudio.com/article/luminositycontrastratioalgorithm.php : + // + // "[Luminosity contrast can be calculated as] (L1+.05) / (L2+.05) where L is + // luminosity and is defined as .2126*R + .7152*G + .0722B using linearised + // R, G, and B values. Linearised R (for example) = (R/FS)^2.2 where FS is + // full scale value (255 for 8 bit color channels). L1 is the higher value + // (of text or background) and L2 is the lower value. + // + // The Gamma correction and RGB constants are derived from the Standard + // Default Color Space for the Internet (sRGB), and the 0.05 offset is + // included to compensate for contrast ratios that occur when a value is at + // or near zero, and for ambient light effects. + const double l1 = Luminosity(color1); + const double l2 = Luminosity(color2); + return (l1 > l2) ? ((l1 + 0.05) / (l2 + 0.05)) : ((l2 + 0.05) / (l1 + 0.05)); +} + +// static +double AutocompletePopupView::DrawLineInfo::Luminosity(COLORREF color) { + // See comments in LuminosityContrast(). + const double linearised_r = + pow(static_cast<double>(GetRValue(color)) / 255.0, 2.2); + const double linearised_g = + pow(static_cast<double>(GetGValue(color)) / 255.0, 2.2); + const double linearised_b = + pow(static_cast<double>(GetBValue(color)) / 255.0, 2.2); + return (0.2126 * linearised_r) + (0.7152 * linearised_g) + + (0.0722 * linearised_b); +} + +COLORREF AutocompletePopupView::DrawLineInfo::AlphaBlend(COLORREF foreground, + COLORREF background, + BYTE alpha) { + if (alpha == 0) + return background; + else if (alpha == 0xff) + return foreground; + + return RGB( + ((GetRValue(foreground) * alpha) + + (GetRValue(background) * (0xff - alpha))) / 0xff, + ((GetGValue(foreground) * alpha) + + (GetGValue(background) * (0xff - alpha))) / 0xff, + ((GetBValue(foreground) * alpha) + + (GetBValue(background) * (0xff - alpha))) / 0xff); +} |
