| OLD | NEW |
| (Empty) |
| 1 // Copyright (c) 2006-2008 The Chromium Authors. All rights reserved. | |
| 2 // Use of this source code is governed by a BSD-style license that can be | |
| 3 // found in the LICENSE file. | |
| 4 | |
| 5 #include "chrome/browser/autocomplete/autocomplete_popup_view_win.h" | |
| 6 | |
| 7 // TODO(deanm): Clean up these includes, not going to fight it now. | |
| 8 #include <atlbase.h> | |
| 9 #include <atlapp.h> | |
| 10 #include <atlcrack.h> | |
| 11 #include <atlmisc.h> | |
| 12 #include <cmath> | |
| 13 | |
| 14 #include "app/gfx/canvas.h" | |
| 15 #include "app/gfx/font.h" | |
| 16 #include "app/l10n_util.h" | |
| 17 #include "app/resource_bundle.h" | |
| 18 #include "base/command_line.h" | |
| 19 #include "base/string_util.h" | |
| 20 #include "base/win_util.h" | |
| 21 #include "chrome/browser/autocomplete/autocomplete.h" | |
| 22 #include "chrome/browser/autocomplete/autocomplete_edit.h" | |
| 23 #include "chrome/browser/autocomplete/autocomplete_popup_model.h" | |
| 24 #include "chrome/browser/browser_process.h" | |
| 25 #include "chrome/browser/net/dns_global.h" | |
| 26 #include "chrome/browser/profile.h" | |
| 27 #include "chrome/browser/search_engines/template_url.h" | |
| 28 #include "chrome/browser/search_engines/template_url_model.h" | |
| 29 #include "chrome/browser/views/autocomplete/autocomplete_popup_contents_view.h" | |
| 30 #include "chrome/browser/views/location_bar_view.h" | |
| 31 #include "chrome/common/chrome_switches.h" | |
| 32 #include "chrome/common/notification_service.h" | |
| 33 #include "grit/theme_resources.h" | |
| 34 #include "third_party/icu38/public/common/unicode/ubidi.h" | |
| 35 #include "views/view.h" | |
| 36 | |
| 37 // Padding between text and the star indicator, in pixels. | |
| 38 static const int kStarPadding = 4; | |
| 39 | |
| 40 // This class implements a utility used for mirroring x-coordinates when the | |
| 41 // application language is a right-to-left one. | |
| 42 class AutocompletePopupViewWin::MirroringContext { | |
| 43 public: | |
| 44 MirroringContext() : min_x_(0), center_x_(0), max_x_(0), enabled_(false) { } | |
| 45 | |
| 46 // Initializes the bounding region used for mirroring coordinates. | |
| 47 // This class uses the center of this region as an axis for calculating | |
| 48 // mirrored coordinates. | |
| 49 void Initialize(int x1, int x2, bool enabled); | |
| 50 | |
| 51 // Return the "left" side of the specified region. | |
| 52 // When the application language is a right-to-left one, this function | |
| 53 // calculates the mirrored coordinates of the input region and returns the | |
| 54 // left side of the mirrored region. | |
| 55 // The input region must be in the bounding region specified in the | |
| 56 // Initialize() function. | |
| 57 int GetLeft(int x1, int x2) const; | |
| 58 | |
| 59 // Returns whether or not we are mirroring the x coordinate. | |
| 60 bool enabled() const { | |
| 61 return enabled_; | |
| 62 } | |
| 63 | |
| 64 private: | |
| 65 int min_x_; | |
| 66 int center_x_; | |
| 67 int max_x_; | |
| 68 bool enabled_; | |
| 69 | |
| 70 DISALLOW_EVIL_CONSTRUCTORS(MirroringContext); | |
| 71 }; | |
| 72 | |
| 73 void AutocompletePopupViewWin::MirroringContext::Initialize(int x1, | |
| 74 int x2, | |
| 75 bool enabled) { | |
| 76 min_x_ = std::min(x1, x2); | |
| 77 max_x_ = std::max(x1, x2); | |
| 78 center_x_ = min_x_ + (max_x_ - min_x_) / 2; | |
| 79 enabled_ = enabled; | |
| 80 } | |
| 81 | |
| 82 int AutocompletePopupViewWin::MirroringContext::GetLeft(int x1, int x2) const { | |
| 83 return enabled_ ? | |
| 84 (center_x_ + (center_x_ - std::max(x1, x2))) : std::min(x1, x2); | |
| 85 } | |
| 86 | |
| 87 const wchar_t AutocompletePopupViewWin::DrawLineInfo::ellipsis_str[] = | |
| 88 L"\x2026"; | |
| 89 | |
| 90 AutocompletePopupViewWin::DrawLineInfo::DrawLineInfo(const gfx::Font& font) { | |
| 91 // Create regular and bold fonts. | |
| 92 regular_font = font.DeriveFont(-1); | |
| 93 bold_font = regular_font.DeriveFont(0, gfx::Font::BOLD); | |
| 94 | |
| 95 // The total padding added to each line (bottom padding is what is | |
| 96 // left over after DrawEntry() specifies its top offset). | |
| 97 static const int kTotalLinePadding = 5; | |
| 98 font_height = std::max(regular_font.height(), bold_font.height()); | |
| 99 line_height = font_height + kTotalLinePadding; | |
| 100 ave_char_width = regular_font.GetExpectedTextWidth(1); | |
| 101 ellipsis_width = std::max(regular_font.GetStringWidth(ellipsis_str), | |
| 102 bold_font.GetStringWidth(ellipsis_str)); | |
| 103 | |
| 104 // Create background colors. | |
| 105 background_colors[NORMAL] = GetSysColor(COLOR_WINDOW); | |
| 106 background_colors[SELECTED] = GetSysColor(COLOR_HIGHLIGHT); | |
| 107 background_colors[HOVERED] = | |
| 108 AlphaBlend(background_colors[SELECTED], background_colors[NORMAL], 0x40); | |
| 109 | |
| 110 // Create text colors. | |
| 111 text_colors[NORMAL] = GetSysColor(COLOR_WINDOWTEXT); | |
| 112 text_colors[HOVERED] = text_colors[NORMAL]; | |
| 113 text_colors[SELECTED] = GetSysColor(COLOR_HIGHLIGHTTEXT); | |
| 114 | |
| 115 // Create brushes and url colors. | |
| 116 const COLORREF dark_url(0x008000); | |
| 117 const COLORREF light_url(0xd0ffd0); | |
| 118 for (int i = 0; i < MAX_STATUS_ENTRIES; ++i) { | |
| 119 // Pick whichever URL color contrasts better. | |
| 120 const double dark_contrast = | |
| 121 LuminosityContrast(dark_url, background_colors[i]); | |
| 122 const double light_contrast = | |
| 123 LuminosityContrast(light_url, background_colors[i]); | |
| 124 url_colors[i] = (dark_contrast > light_contrast) ? dark_url : light_url; | |
| 125 | |
| 126 brushes[i] = CreateSolidBrush(background_colors[i]); | |
| 127 } | |
| 128 } | |
| 129 | |
| 130 AutocompletePopupViewWin::DrawLineInfo::~DrawLineInfo() { | |
| 131 for (int i = 0; i < MAX_STATUS_ENTRIES; ++i) | |
| 132 DeleteObject(brushes[i]); | |
| 133 } | |
| 134 | |
| 135 // static | |
| 136 double AutocompletePopupViewWin::DrawLineInfo::LuminosityContrast( | |
| 137 COLORREF color1, | |
| 138 COLORREF color2) { | |
| 139 // This algorithm was adapted from the following text at | |
| 140 // http://juicystudio.com/article/luminositycontrastratioalgorithm.php : | |
| 141 // | |
| 142 // "[Luminosity contrast can be calculated as] (L1+.05) / (L2+.05) where L is | |
| 143 // luminosity and is defined as .2126*R + .7152*G + .0722B using linearised | |
| 144 // R, G, and B values. Linearised R (for example) = (R/FS)^2.2 where FS is | |
| 145 // full scale value (255 for 8 bit color channels). L1 is the higher value | |
| 146 // (of text or background) and L2 is the lower value. | |
| 147 // | |
| 148 // The Gamma correction and RGB constants are derived from the Standard | |
| 149 // Default Color Space for the Internet (sRGB), and the 0.05 offset is | |
| 150 // included to compensate for contrast ratios that occur when a value is at | |
| 151 // or near zero, and for ambient light effects. | |
| 152 const double l1 = Luminosity(color1); | |
| 153 const double l2 = Luminosity(color2); | |
| 154 return (l1 > l2) ? ((l1 + 0.05) / (l2 + 0.05)) : ((l2 + 0.05) / (l1 + 0.05)); | |
| 155 } | |
| 156 | |
| 157 // static | |
| 158 double AutocompletePopupViewWin::DrawLineInfo::Luminosity(COLORREF color) { | |
| 159 // See comments in LuminosityContrast(). | |
| 160 const double linearised_r = | |
| 161 pow(static_cast<double>(GetRValue(color)) / 255.0, 2.2); | |
| 162 const double linearised_g = | |
| 163 pow(static_cast<double>(GetGValue(color)) / 255.0, 2.2); | |
| 164 const double linearised_b = | |
| 165 pow(static_cast<double>(GetBValue(color)) / 255.0, 2.2); | |
| 166 return (0.2126 * linearised_r) + (0.7152 * linearised_g) + | |
| 167 (0.0722 * linearised_b); | |
| 168 } | |
| 169 | |
| 170 COLORREF AutocompletePopupViewWin::DrawLineInfo::AlphaBlend( | |
| 171 COLORREF foreground, | |
| 172 COLORREF background, | |
| 173 BYTE alpha) { | |
| 174 if (alpha == 0) | |
| 175 return background; | |
| 176 else if (alpha == 0xff) | |
| 177 return foreground; | |
| 178 | |
| 179 return RGB( | |
| 180 ((GetRValue(foreground) * alpha) + | |
| 181 (GetRValue(background) * (0xff - alpha))) / 0xff, | |
| 182 ((GetGValue(foreground) * alpha) + | |
| 183 (GetGValue(background) * (0xff - alpha))) / 0xff, | |
| 184 ((GetBValue(foreground) * alpha) + | |
| 185 (GetBValue(background) * (0xff - alpha))) / 0xff); | |
| 186 } | |
| 187 | |
| 188 AutocompletePopupViewWin::AutocompletePopupViewWin( | |
| 189 const gfx::Font& font, | |
| 190 AutocompleteEditViewWin* edit_view, | |
| 191 AutocompleteEditModel* edit_model, | |
| 192 Profile* profile) | |
| 193 : model_(new AutocompletePopupModel(this, edit_model, profile)), | |
| 194 edit_view_(edit_view), | |
| 195 line_info_(font), | |
| 196 mirroring_context_(new MirroringContext()), | |
| 197 star_(ResourceBundle::GetSharedInstance().GetBitmapNamed( | |
| 198 IDR_CONTENT_STAR_ON)) { | |
| 199 } | |
| 200 | |
| 201 void AutocompletePopupViewWin::InvalidateLine(size_t line) { | |
| 202 RECT rc; | |
| 203 GetClientRect(&rc); | |
| 204 rc.top = LineTopPixel(line); | |
| 205 rc.bottom = rc.top + line_info_.line_height; | |
| 206 InvalidateRect(&rc, false); | |
| 207 } | |
| 208 | |
| 209 void AutocompletePopupViewWin::UpdatePopupAppearance() { | |
| 210 const AutocompleteResult& result = model_->result(); | |
| 211 if (result.empty()) { | |
| 212 // No matches, close any existing popup. | |
| 213 if (m_hWnd) { | |
| 214 DestroyWindow(); | |
| 215 m_hWnd = NULL; | |
| 216 } | |
| 217 return; | |
| 218 } | |
| 219 | |
| 220 // Figure the coordinates of the popup: | |
| 221 // Get the coordinates of the location bar view; these are returned relative | |
| 222 // to its parent. | |
| 223 // TODO(pkasting): http://b/1345937 All this use of editor accessors should | |
| 224 // die once this class is a true ChromeView. | |
| 225 CRect rc = edit_view_->parent_view()->bounds().ToRECT(); | |
| 226 // Subtract the top left corner to make the coordinates relative to the | |
| 227 // location bar view itself, and convert to screen coordinates. | |
| 228 gfx::Point top_left(-rc.TopLeft()); | |
| 229 views::View::ConvertPointToScreen(edit_view_->parent_view(), &top_left); | |
| 230 rc.OffsetRect(top_left.ToPOINT()); | |
| 231 // Expand by one pixel on each side since that's the amount the location bar | |
| 232 // view is inset from the divider line that edges the adjacent buttons. | |
| 233 // Deflate the top and bottom by the height of the extra graphics around the | |
| 234 // edit. | |
| 235 // TODO(pkasting): http://b/972786 This shouldn't be hardcoded to rely on | |
| 236 // LocationBarView constants. Instead we should just make the edit be "at the | |
| 237 // right coordinates", or something else generic. | |
| 238 rc.InflateRect(1, -LocationBarView::kVertMargin); | |
| 239 // Now rc is the exact width we want and is positioned like the edit would | |
| 240 // be, so shift the top and bottom downwards so the new top is where the old | |
| 241 // bottom is and the rect has the height we need for all our entries, plus a | |
| 242 // one-pixel border on top and bottom. | |
| 243 rc.top = rc.bottom; | |
| 244 rc.bottom += static_cast<int>(result.size()) * line_info_.line_height + 2; | |
| 245 | |
| 246 if (!m_hWnd) { | |
| 247 // To prevent this window from being activated, we create an invisible | |
| 248 // window and manually show it without activating it. | |
| 249 Create(edit_view_->m_hWnd, rc, AUTOCOMPLETEPOPUPVIEW_CLASSNAME, WS_POPUP, | |
| 250 WS_EX_TOOLWINDOW); | |
| 251 // When an IME is attached to the rich-edit control, retrieve its window | |
| 252 // handle and show this popup window under the IME windows. | |
| 253 // Otherwise, show this popup window under top-most windows. | |
| 254 // TODO(hbono): http://b/1111369 if we exclude this popup window from the | |
| 255 // display area of IME windows, this workaround becomes unnecessary. | |
| 256 HWND ime_window = ImmGetDefaultIMEWnd(edit_view_->m_hWnd); | |
| 257 SetWindowPos(ime_window ? ime_window : HWND_NOTOPMOST, 0, 0, 0, 0, | |
| 258 SWP_NOSIZE | SWP_NOMOVE | SWP_NOACTIVATE | SWP_SHOWWINDOW); | |
| 259 } else { | |
| 260 // Already open, just resize the window. This is a bit tricky; we want to | |
| 261 // repaint the whole window, since the contents may have changed, but | |
| 262 // MoveWindow() won't repaint portions that haven't moved or been | |
| 263 // added/removed. So we first call InvalidateRect(), so the next repaint | |
| 264 // paints the whole window, then tell MoveWindow() to do the actual | |
| 265 // repaint, which will also properly repaint Windows formerly under the | |
| 266 // popup. | |
| 267 InvalidateRect(NULL, false); | |
| 268 MoveWindow(rc.left, rc.top, rc.right - rc.left, rc.bottom - rc.top, true); | |
| 269 } | |
| 270 | |
| 271 // TODO(pkasting): http://b/1111369 We should call ImmSetCandidateWindow() on | |
| 272 // the edit_view_'s IME context here, and exclude ourselves from its display | |
| 273 // area. Not clear what to pass for the lpCandidate->ptCurrentPos member, | |
| 274 // though... | |
| 275 } | |
| 276 | |
| 277 void AutocompletePopupViewWin::OnHoverEnabledOrDisabled(bool disabled) { | |
| 278 TRACKMOUSEEVENT tme; | |
| 279 tme.cbSize = sizeof(TRACKMOUSEEVENT); | |
| 280 if (disabled) { | |
| 281 // Save the current mouse position to check against for re-enabling. | |
| 282 GetCursorPos(&last_hover_coordinates_); // Returns screen coordinates | |
| 283 | |
| 284 // Cancel existing registration for WM_MOUSELEAVE notifications. | |
| 285 tme.dwFlags = TME_CANCEL | TME_LEAVE; | |
| 286 } else { | |
| 287 // Register for WM_MOUSELEAVE notifications. | |
| 288 tme.dwFlags = TME_LEAVE; | |
| 289 } | |
| 290 tme.hwndTrack = m_hWnd; | |
| 291 tme.dwHoverTime = HOVER_DEFAULT; // Not actually used | |
| 292 TrackMouseEvent(&tme); | |
| 293 } | |
| 294 | |
| 295 void AutocompletePopupViewWin::OnLButtonDown(UINT keys, const CPoint& point) { | |
| 296 const size_t new_hovered_line = PixelToLine(point.y); | |
| 297 model_->SetHoveredLine(new_hovered_line); | |
| 298 model_->SetSelectedLine(new_hovered_line, false); | |
| 299 } | |
| 300 | |
| 301 void AutocompletePopupViewWin::OnMButtonDown(UINT keys, const CPoint& point) { | |
| 302 model_->SetHoveredLine(PixelToLine(point.y)); | |
| 303 } | |
| 304 | |
| 305 void AutocompletePopupViewWin::OnLButtonUp(UINT keys, const CPoint& point) { | |
| 306 OnButtonUp(point, CURRENT_TAB); | |
| 307 } | |
| 308 | |
| 309 void AutocompletePopupViewWin::OnMButtonUp(UINT keys, const CPoint& point) { | |
| 310 OnButtonUp(point, NEW_BACKGROUND_TAB); | |
| 311 } | |
| 312 | |
| 313 LRESULT AutocompletePopupViewWin::OnMouseActivate(HWND window, | |
| 314 UINT hit_test, | |
| 315 UINT mouse_message) { | |
| 316 return MA_NOACTIVATE; | |
| 317 } | |
| 318 | |
| 319 void AutocompletePopupViewWin::OnMouseLeave() { | |
| 320 // The mouse has left the window, so no line is hovered. | |
| 321 model_->SetHoveredLine(AutocompletePopupModel::kNoMatch); | |
| 322 } | |
| 323 | |
| 324 void AutocompletePopupViewWin::OnMouseMove(UINT keys, const CPoint& point) { | |
| 325 // Track hover when | |
| 326 // (a) The left or middle button is down (the user is interacting via the | |
| 327 // mouse) | |
| 328 // (b) The user moves the mouse from where we last stopped tracking hover | |
| 329 // (c) We started tracking previously due to (a) or (b) and haven't stopped | |
| 330 // yet (user hasn't used the keyboard to interact again) | |
| 331 const bool action_button_pressed = !!(keys & (MK_MBUTTON | MK_LBUTTON)); | |
| 332 CPoint screen_point(point); | |
| 333 ClientToScreen(&screen_point); | |
| 334 if (action_button_pressed || (last_hover_coordinates_ != screen_point) || | |
| 335 (model_->hovered_line() != AutocompletePopupModel::kNoMatch)) { | |
| 336 // Determine the hovered line from the y coordinate of the event. We don't | |
| 337 // need to check whether the x coordinates are within the window since if | |
| 338 // they weren't someone else would have received the WM_MOUSEMOVE. | |
| 339 const size_t new_hovered_line = PixelToLine(point.y); | |
| 340 model_->SetHoveredLine(new_hovered_line); | |
| 341 | |
| 342 // When the user has the left button down, update their selection | |
| 343 // immediately (don't wait for mouseup). | |
| 344 if (keys & MK_LBUTTON) | |
| 345 model_->SetSelectedLine(new_hovered_line, false); | |
| 346 } | |
| 347 } | |
| 348 | |
| 349 void AutocompletePopupViewWin::OnPaint(HDC other_dc) { | |
| 350 const AutocompleteResult& result = model_->result(); | |
| 351 CHECK(!result.empty()); // Shouldn't be drawing an empty popup; any empty | |
| 352 // result set should have synchronously closed us. | |
| 353 | |
| 354 CPaintDC dc(m_hWnd); | |
| 355 | |
| 356 RECT rc; | |
| 357 GetClientRect(&rc); | |
| 358 mirroring_context_->Initialize(rc.left, rc.right, | |
| 359 l10n_util::GetTextDirection() == l10n_util::RIGHT_TO_LEFT); | |
| 360 DrawBorder(rc, dc); | |
| 361 | |
| 362 bool all_descriptions_empty = true; | |
| 363 for (AutocompleteResult::const_iterator i(result.begin()); i != result.end(); | |
| 364 ++i) { | |
| 365 if (!i->description.empty()) { | |
| 366 all_descriptions_empty = false; | |
| 367 break; | |
| 368 } | |
| 369 } | |
| 370 | |
| 371 // Only repaint the invalid lines. | |
| 372 // In rare cases, it seems possible to get line offsets off the end of the | |
| 373 // popup. I suspect this can happen when the user invalidates a new line | |
| 374 // (e.g. by moving the mouse) and, before the paint request is serviced, hits | |
| 375 // a key that causes autocomplete to run, causing the results list to become | |
| 376 // shorter (at least initially). So sanitize the line numbers here. | |
| 377 const size_t last_valid_line = result.size() - 1; | |
| 378 const size_t first_line = PixelToLine(dc.m_ps.rcPaint.top); | |
| 379 if (first_line > last_valid_line) | |
| 380 return; | |
| 381 const size_t last_line = | |
| 382 std::min(PixelToLine(dc.m_ps.rcPaint.bottom), last_valid_line); | |
| 383 | |
| 384 for (size_t i = first_line; i <= last_line; ++i) { | |
| 385 DrawLineInfo::LineStatus status; | |
| 386 // Selection should take precedence over hover. | |
| 387 if (i == model_->selected_line()) | |
| 388 status = DrawLineInfo::SELECTED; | |
| 389 else if (i == model_->hovered_line()) | |
| 390 status = DrawLineInfo::HOVERED; | |
| 391 else | |
| 392 status = DrawLineInfo::NORMAL; | |
| 393 DrawEntry(dc, rc, i, status, all_descriptions_empty, | |
| 394 result.match_at(i).starred); | |
| 395 } | |
| 396 } | |
| 397 | |
| 398 void AutocompletePopupViewWin::OnButtonUp(const CPoint& point, | |
| 399 WindowOpenDisposition disposition) { | |
| 400 const size_t line = PixelToLine(point.y); | |
| 401 const AutocompleteMatch& match = model_->result().match_at(line); | |
| 402 // OpenURL() may close the popup, which will clear the result set and, by | |
| 403 // extension, |match| and its contents. So copy the relevant strings out to | |
| 404 // make sure they stay alive until the call completes. | |
| 405 const GURL url(match.destination_url); | |
| 406 std::wstring keyword; | |
| 407 const bool is_keyword_hint = model_->GetKeywordForMatch(match, &keyword); | |
| 408 edit_view_->OpenURL(url, disposition, match.transition, GURL(), line, | |
| 409 is_keyword_hint ? std::wstring() : keyword); | |
| 410 } | |
| 411 | |
| 412 int AutocompletePopupViewWin::LineTopPixel(size_t line) const { | |
| 413 // The popup has a 1 px top border. | |
| 414 return line_info_.line_height * static_cast<int>(line) + 1; | |
| 415 } | |
| 416 | |
| 417 size_t AutocompletePopupViewWin::PixelToLine(int y) const { | |
| 418 const size_t line = std::max(y - 1, 0) / line_info_.line_height; | |
| 419 return std::min(line, model_->result().size() - 1); | |
| 420 } | |
| 421 | |
| 422 // Draws a light border around the inside of the window with the given client | |
| 423 // rectangle and DC. | |
| 424 void AutocompletePopupViewWin::DrawBorder(const RECT& rc, HDC dc) { | |
| 425 HPEN hpen = CreatePen(PS_SOLID, 1, RGB(199, 202, 206)); | |
| 426 HGDIOBJ old_pen = SelectObject(dc, hpen); | |
| 427 | |
| 428 int width = rc.right - rc.left - 1; | |
| 429 int height = rc.bottom - rc.top - 1; | |
| 430 | |
| 431 MoveToEx(dc, 0, 0, NULL); | |
| 432 LineTo(dc, 0, height); | |
| 433 LineTo(dc, width, height); | |
| 434 LineTo(dc, width, 0); | |
| 435 LineTo(dc, 0, 0); | |
| 436 | |
| 437 SelectObject(dc, old_pen); | |
| 438 DeleteObject(hpen); | |
| 439 } | |
| 440 | |
| 441 int AutocompletePopupViewWin::DrawString(HDC dc, | |
| 442 int x, | |
| 443 int y, | |
| 444 int max_x, | |
| 445 const wchar_t* text, | |
| 446 int length, | |
| 447 int style, | |
| 448 const DrawLineInfo::LineStatus status, | |
| 449 const MirroringContext* context, | |
| 450 bool text_direction_is_rtl) const { | |
| 451 if (length <= 0) | |
| 452 return 0; | |
| 453 | |
| 454 // Set up the text decorations. | |
| 455 SelectObject(dc, (style & ACMatchClassification::MATCH) ? | |
| 456 line_info_.bold_font.hfont() : line_info_.regular_font.hfont()); | |
| 457 const COLORREF foreground = (style & ACMatchClassification::URL) ? | |
| 458 line_info_.url_colors[status] : line_info_.text_colors[status]; | |
| 459 const COLORREF background = line_info_.background_colors[status]; | |
| 460 SetTextColor(dc, (style & ACMatchClassification::DIM) ? | |
| 461 DrawLineInfo::AlphaBlend(foreground, background, 0xAA) : foreground); | |
| 462 | |
| 463 // Retrieve the width of the decorated text and display it. When we cannot | |
| 464 // display this fragment in the given width, we trim the fragment and add an | |
| 465 // ellipsis. | |
| 466 // | |
| 467 // TODO(hbono): http:///b/1222425 We should change the following eliding code | |
| 468 // with more aggressive one. | |
| 469 int text_x = x; | |
| 470 int max_length = 0; | |
| 471 SIZE text_size = {0}; | |
| 472 GetTextExtentExPoint(dc, text, length, | |
| 473 max_x - line_info_.ellipsis_width - text_x, &max_length, | |
| 474 NULL, &text_size); | |
| 475 | |
| 476 if (max_length < length) | |
| 477 GetTextExtentPoint32(dc, text, max_length, &text_size); | |
| 478 | |
| 479 const int mirrored_x = context->GetLeft(text_x, text_x + text_size.cx); | |
| 480 RECT text_bounds = {mirrored_x, | |
| 481 0, | |
| 482 mirrored_x + text_size.cx, | |
| 483 line_info_.line_height}; | |
| 484 | |
| 485 int flags = DT_SINGLELINE | DT_NOPREFIX; | |
| 486 if (text_direction_is_rtl) | |
| 487 // In order to make sure RTL text is displayed correctly (for example, a | |
| 488 // trailing space should be displayed on the left and not on the right), we | |
| 489 // pass the flag DT_RTLREADING. | |
| 490 flags |= DT_RTLREADING; | |
| 491 | |
| 492 DrawText(dc, text, length, &text_bounds, flags); | |
| 493 text_x += text_size.cx; | |
| 494 | |
| 495 // Draw the ellipsis. Note that since we use the mirroring context, the | |
| 496 // ellipsis are drawn either to the right or to the left of the text. | |
| 497 if (max_length < length) { | |
| 498 TextOut(dc, context->GetLeft(text_x, text_x + line_info_.ellipsis_width), | |
| 499 0, line_info_.ellipsis_str, arraysize(line_info_.ellipsis_str) - 1); | |
| 500 text_x += line_info_.ellipsis_width; | |
| 501 } | |
| 502 | |
| 503 return text_x - x; | |
| 504 } | |
| 505 | |
| 506 void AutocompletePopupViewWin::DrawMatchFragments( | |
| 507 HDC dc, | |
| 508 const std::wstring& text, | |
| 509 const ACMatchClassifications& classifications, | |
| 510 int x, | |
| 511 int y, | |
| 512 int max_x, | |
| 513 DrawLineInfo::LineStatus status) const { | |
| 514 if (!text.length()) | |
| 515 return; | |
| 516 | |
| 517 // Check whether or not this text is a URL string. | |
| 518 // A URL string is basically in English with possible included words in | |
| 519 // Arabic or Hebrew. For such case, ICU provides a special algorithm and we | |
| 520 // should use it. | |
| 521 bool url = false; | |
| 522 for (ACMatchClassifications::const_iterator i = classifications.begin(); | |
| 523 i != classifications.end(); ++i) { | |
| 524 if (i->style & ACMatchClassification::URL) | |
| 525 url = true; | |
| 526 } | |
| 527 | |
| 528 // Initialize a bidirectional line iterator of ICU and split the text into | |
| 529 // visual runs. (A visual run is consecutive characters which have the same | |
| 530 // display direction and should be displayed at once.) | |
| 531 l10n_util::BiDiLineIterator bidi_line; | |
| 532 if (!bidi_line.Open(text, mirroring_context_->enabled(), url)) | |
| 533 return; | |
| 534 const int runs = bidi_line.CountRuns(); | |
| 535 | |
| 536 // Draw the visual runs. | |
| 537 // This loop splits each run into text fragments with the given | |
| 538 // classifications and draws the text fragments. | |
| 539 // When the direction of a run is right-to-left, we have to mirror the | |
| 540 // x-coordinate of this run and render the fragments in the right-to-left | |
| 541 // reading order. To handle this display order independently from the one of | |
| 542 // this popup window, this loop renders a run with the steps below: | |
| 543 // 1. Create a local display context for each run; | |
| 544 // 2. Render the run into the local display context, and; | |
| 545 // 3. Copy the local display context to the one of the popup window. | |
| 546 int run_x = x; | |
| 547 for (int run = 0; run < runs; ++run) { | |
| 548 int run_start = 0; | |
| 549 int run_length = 0; | |
| 550 | |
| 551 // The index we pass to GetVisualRun corresponds to the position of the run | |
| 552 // in the displayed text. For example, the string "Google in HEBREW" (where | |
| 553 // HEBREW is text in the Hebrew language) has two runs: "Google in " which | |
| 554 // is an LTR run, and "HEBREW" which is an RTL run. In an LTR context, the | |
| 555 // run "Google in " has the index 0 (since it is the leftmost run | |
| 556 // displayed). In an RTL context, the same run has the index 1 because it | |
| 557 // is the rightmost run. This is why the order in which we traverse the | |
| 558 // runs is different depending on the locale direction. | |
| 559 // | |
| 560 // Note that for URLs we always traverse the runs from lower to higher | |
| 561 // indexes because the return order of runs for a URL always matches the | |
| 562 // physical order of the context. | |
| 563 int current_run = | |
| 564 (mirroring_context_->enabled() && !url) ? (runs - run - 1) : run; | |
| 565 const UBiDiDirection run_direction = bidi_line.GetVisualRun(current_run, | |
| 566 &run_start, | |
| 567 &run_length); | |
| 568 const int run_end = run_start + run_length; | |
| 569 | |
| 570 // Set up a local display context for rendering this run. | |
| 571 int text_x = 0; | |
| 572 const int text_max_x = max_x - run_x; | |
| 573 MirroringContext run_context; | |
| 574 run_context.Initialize(0, text_max_x, run_direction == UBIDI_RTL); | |
| 575 | |
| 576 // In addition to creating a mirroring context for the run, we indicate | |
| 577 // whether the run needs to be rendered as RTL text. The mirroring context | |
| 578 // alone in not sufficient because there are cases where a mirrored RTL run | |
| 579 // needs to be rendered in an LTR context (for example, an RTL run within | |
| 580 // an URL). | |
| 581 bool run_direction_is_rtl = (run_direction == UBIDI_RTL) && !url; | |
| 582 CDC text_dc(CreateCompatibleDC(dc)); | |
| 583 CBitmap text_bitmap(CreateCompatibleBitmap(dc, text_max_x, | |
| 584 line_info_.font_height)); | |
| 585 SelectObject(text_dc, text_bitmap); | |
| 586 const RECT text_rect = {0, 0, text_max_x, line_info_.line_height}; | |
| 587 FillRect(text_dc, &text_rect, line_info_.brushes[status]); | |
| 588 SetBkMode(text_dc, TRANSPARENT); | |
| 589 | |
| 590 // Split this run with the given classifications and draw the fragments | |
| 591 // into the local display context. | |
| 592 for (ACMatchClassifications::const_iterator i = classifications.begin(); | |
| 593 i != classifications.end(); ++i) { | |
| 594 const int text_start = std::max(run_start, static_cast<int>(i->offset)); | |
| 595 const int text_end = std::min(run_end, (i != classifications.end() - 1) ? | |
| 596 static_cast<int>((i + 1)->offset) : run_end); | |
| 597 text_x += DrawString(text_dc, text_x, 0, text_max_x, &text[text_start], | |
| 598 text_end - text_start, i->style, status, | |
| 599 &run_context, run_direction_is_rtl); | |
| 600 } | |
| 601 | |
| 602 // Copy the local display context to the one of the popup window and | |
| 603 // delete the local display context. | |
| 604 BitBlt(dc, mirroring_context_->GetLeft(run_x, run_x + text_x), y, text_x, | |
| 605 line_info_.line_height, text_dc, run_context.GetLeft(0, text_x), 0, | |
| 606 SRCCOPY); | |
| 607 run_x += text_x; | |
| 608 } | |
| 609 } | |
| 610 | |
| 611 void AutocompletePopupViewWin::DrawEntry(HDC dc, | |
| 612 const RECT& client_rect, | |
| 613 size_t line, | |
| 614 DrawLineInfo::LineStatus status, | |
| 615 bool all_descriptions_empty, | |
| 616 bool starred) const { | |
| 617 // Calculate outer bounds of entry, and fill background. | |
| 618 const int top_pixel = LineTopPixel(line); | |
| 619 const RECT rc = {1, top_pixel, client_rect.right - client_rect.left - 1, | |
| 620 top_pixel + line_info_.line_height}; | |
| 621 FillRect(dc, &rc, line_info_.brushes[status]); | |
| 622 | |
| 623 // Calculate and display contents/description sections as follows: | |
| 624 // * 2 px top margin, bottom margin is handled by line_height. | |
| 625 const int y = rc.top + 2; | |
| 626 | |
| 627 // * 1 char left/right margin. | |
| 628 const int side_margin = line_info_.ave_char_width; | |
| 629 | |
| 630 // * 50% of the remaining width is initially allocated to each section, with | |
| 631 // a 2 char margin followed by the star column and kStarPadding padding. | |
| 632 const int content_min_x = rc.left + side_margin; | |
| 633 const int description_max_x = rc.right - side_margin; | |
| 634 const int mid_line = (description_max_x - content_min_x) / 2 + content_min_x; | |
| 635 const int star_col_width = kStarPadding + star_->width(); | |
| 636 const int content_right_margin = line_info_.ave_char_width * 2; | |
| 637 | |
| 638 // * If this would make the content section display fewer than 40 characters, | |
| 639 // the content section is increased to that minimum at the expense of the | |
| 640 // description section. | |
| 641 const int content_width = | |
| 642 std::max(mid_line - content_min_x - content_right_margin, | |
| 643 line_info_.ave_char_width * 40); | |
| 644 const int description_width = description_max_x - content_min_x - | |
| 645 content_width - star_col_width; | |
| 646 | |
| 647 // * If this would make the description section display fewer than 20 | |
| 648 // characters, or if there are no descriptions to display or the result is | |
| 649 // the HISTORY_SEARCH shortcut, the description section is eliminated, and | |
| 650 // all the available width is used for the content section. | |
| 651 int star_x; | |
| 652 const AutocompleteMatch& match = model_->result().match_at(line); | |
| 653 if ((description_width < (line_info_.ave_char_width * 20)) || | |
| 654 all_descriptions_empty || | |
| 655 (match.type == AutocompleteMatch::OPEN_HISTORY_PAGE)) { | |
| 656 star_x = description_max_x - star_col_width + kStarPadding; | |
| 657 DrawMatchFragments(dc, match.contents, match.contents_class, content_min_x, | |
| 658 y, star_x - kStarPadding, status); | |
| 659 } else { | |
| 660 star_x = description_max_x - description_width - star_col_width; | |
| 661 DrawMatchFragments(dc, match.contents, match.contents_class, content_min_x, | |
| 662 y, content_min_x + content_width, status); | |
| 663 DrawMatchFragments(dc, match.description, match.description_class, | |
| 664 description_max_x - description_width, y, | |
| 665 description_max_x, status); | |
| 666 } | |
| 667 if (starred) | |
| 668 DrawStar(dc, star_x, | |
| 669 (line_info_.line_height - star_->height()) / 2 + top_pixel); | |
| 670 } | |
| 671 | |
| 672 void AutocompletePopupViewWin::DrawStar(HDC dc, int x, int y) const { | |
| 673 gfx::Canvas canvas(star_->width(), star_->height(), false); | |
| 674 // Make the background completely transparent. | |
| 675 canvas.drawColor(SK_ColorBLACK, SkXfermode::kClear_Mode); | |
| 676 canvas.DrawBitmapInt(*star_, 0, 0); | |
| 677 canvas.getTopPlatformDevice().drawToHDC( | |
| 678 dc, mirroring_context_->GetLeft(x, x + star_->width()), y, NULL); | |
| 679 } | |
| 680 | |
| 681 // static | |
| 682 AutocompletePopupView* AutocompletePopupView::CreatePopupView( | |
| 683 const gfx::Font& font, | |
| 684 AutocompleteEditViewWin* edit_view, | |
| 685 AutocompleteEditModel* edit_model, | |
| 686 Profile* profile, | |
| 687 AutocompletePopupPositioner* popup_positioner) { | |
| 688 if (CommandLine::ForCurrentProcess()->HasSwitch(switches::kDisableOmnibox2)) | |
| 689 return new AutocompletePopupViewWin(font, edit_view, edit_model, profile); | |
| 690 return new AutocompletePopupContentsView(font, edit_view, edit_model, | |
| 691 profile, popup_positioner); | |
| 692 } | |
| OLD | NEW |