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 |