Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(271)

Side by Side Diff: ui/display/win/screen_win.cc

Issue 2012083002: Multiple DPI Tracking for ScreenWin (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Created 4 years, 6 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. 1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be 2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file. 3 // found in the LICENSE file.
4 4
5 #include "ui/display/win/screen_win.h" 5 #include "ui/display/win/screen_win.h"
6 6
7 #include <windows.h> 7 #include <windows.h>
8 8
9 #include <algorithm> 9 #include <algorithm>
10 10
11 #include "base/bind.h" 11 #include "base/bind.h"
12 #include "base/bind_helpers.h" 12 #include "base/bind_helpers.h"
13 #include "ui/display/display.h" 13 #include "ui/display/display.h"
14 #include "ui/display/manager/display_layout.h" 14 #include "ui/display/manager/display_layout.h"
15 #include "ui/display/manager/display_layout_builder.h" 15 #include "ui/display/manager/display_layout_builder.h"
16 #include "ui/display/win/display_info.h" 16 #include "ui/display/win/display_info.h"
17 #include "ui/display/win/dpi.h" 17 #include "ui/display/win/dpi.h"
18 #include "ui/display/win/scaling_util.h" 18 #include "ui/display/win/scaling_util.h"
19 #include "ui/display/win/screen_win_display.h" 19 #include "ui/display/win/screen_win_display.h"
20 #include "ui/gfx/geometry/point.h" 20 #include "ui/gfx/geometry/point.h"
21 #include "ui/gfx/geometry/point_conversions.h"
22 #include "ui/gfx/geometry/rect.h" 21 #include "ui/gfx/geometry/rect.h"
23 #include "ui/gfx/geometry/rect_conversions.h" 22 #include "ui/gfx/geometry/size.h"
24 #include "ui/gfx/geometry/size_conversions.h" 23 #include "ui/gfx/geometry/vector2d.h"
25 24
26 namespace display { 25 namespace display {
27 namespace win { 26 namespace win {
28 namespace { 27 namespace {
29 28
29 ScreenWin* g_screen_win_instance = nullptr;
oshima 2016/05/26 21:52:58 nit: screen_win_instance_ https://www.chromium.or
robliao 2016/05/26 22:28:47 https://google.github.io/styleguide/cppguide.html#
oshima 2016/05/26 22:54:47 which I'm trying to fix when I see it to be more c
robliao 2016/05/26 23:19:24 As commonly used, a global variable doesn't guaran
oshima 2016/05/26 23:43:13 extenral linkage has nothing to do with g_ naming.
robliao 2016/05/27 00:47:13 Can you cite the rule you're discussing in those t
oshima 2016/05/27 02:02:04 First of all, google style guide has *no recommend
robliao 2016/05/27 17:15:30 Please note that the reference you cited from the
30
30 std::vector<DisplayInfo> FindAndRemoveTouchingDisplayInfos( 31 std::vector<DisplayInfo> FindAndRemoveTouchingDisplayInfos(
31 const DisplayInfo& ref_display_info, 32 const DisplayInfo& ref_display_info,
32 std::vector<DisplayInfo>* display_infos) { 33 std::vector<DisplayInfo>* display_infos) {
33 std::vector<DisplayInfo> touching_display_infos; 34 std::vector<DisplayInfo> touching_display_infos;
34 display_infos->erase( 35 display_infos->erase(
35 std::remove_if(display_infos->begin(), display_infos->end(), 36 std::remove_if(display_infos->begin(), display_infos->end(),
36 [&touching_display_infos, ref_display_info]( 37 [&touching_display_infos, ref_display_info](
37 const DisplayInfo& display_info) { 38 const DisplayInfo& display_info) {
38 if (DisplayInfosTouch(ref_display_info, display_info)) { 39 if (DisplayInfosTouch(ref_display_info, display_info)) {
39 touching_display_infos.push_back(display_info); 40 touching_display_infos.push_back(display_info);
(...skipping 94 matching lines...) Expand 10 before | Expand all | Expand 10 after
134 return monitor_info; 135 return monitor_info;
135 } 136 }
136 137
137 BOOL CALLBACK EnumMonitorCallback(HMONITOR monitor, 138 BOOL CALLBACK EnumMonitorCallback(HMONITOR monitor,
138 HDC hdc, 139 HDC hdc,
139 LPRECT rect, 140 LPRECT rect,
140 LPARAM data) { 141 LPARAM data) {
141 std::vector<DisplayInfo>* display_infos = 142 std::vector<DisplayInfo>* display_infos =
142 reinterpret_cast<std::vector<DisplayInfo>*>(data); 143 reinterpret_cast<std::vector<DisplayInfo>*>(data);
143 DCHECK(display_infos); 144 DCHECK(display_infos);
145 // TODO(robliao): When ready, replace the GetDPIScale with GetDpiForMonitor
146 // to get the actual DPI for the HMONITOR.
144 display_infos->push_back(DisplayInfo(MonitorInfoFromHMONITOR(monitor), 147 display_infos->push_back(DisplayInfo(MonitorInfoFromHMONITOR(monitor),
145 GetDPIScale())); 148 GetDPIScale()));
146 return TRUE; 149 return TRUE;
147 } 150 }
148 151
149 std::vector<DisplayInfo> GetDisplayInfosFromSystem() { 152 std::vector<DisplayInfo> GetDisplayInfosFromSystem() {
150 std::vector<DisplayInfo> display_infos; 153 std::vector<DisplayInfo> display_infos;
151 EnumDisplayMonitors(nullptr, nullptr, EnumMonitorCallback, 154 EnumDisplayMonitors(nullptr, nullptr, EnumMonitorCallback,
152 reinterpret_cast<LPARAM>(&display_infos)); 155 reinterpret_cast<LPARAM>(&display_infos));
153 DCHECK_EQ(static_cast<size_t>(::GetSystemMetrics(SM_CMONITORS)), 156 DCHECK_EQ(static_cast<size_t>(::GetSystemMetrics(SM_CMONITORS)),
154 display_infos.size()); 157 display_infos.size());
155 return display_infos; 158 return display_infos;
156 } 159 }
157 160
161 // Returns a point in |to_origin|'s coordinates and position scaled by
162 // |scale_factor|.
163 gfx::Point ScalePointRelative(const gfx::Point& from_origin,
164 const gfx::Point& to_origin,
165 const float scale_factor,
166 const gfx::Point& point) {
167 gfx::Vector2d from_origin_vector(from_origin.x(), from_origin.y());
168 gfx::Vector2d to_origin_vector(to_origin.x(), to_origin.y());
169 gfx::Point scaled_relative_point(
170 gfx::ScaleToFlooredPoint(point - from_origin_vector, scale_factor));
171 return scaled_relative_point + to_origin_vector;
172 }
173
158 } // namespace 174 } // namespace
159 175
160 ScreenWin::ScreenWin() { 176 ScreenWin::ScreenWin() {
177 DCHECK(!g_screen_win_instance);
178 g_screen_win_instance = this;
161 Initialize(); 179 Initialize();
162 } 180 }
163 181
164 ScreenWin::~ScreenWin() = default; 182 ScreenWin::~ScreenWin() {
183 DCHECK(g_screen_win_instance == this);
oshima 2016/05/26 21:52:58 DCHECK_EQ
robliao 2016/05/26 22:28:47 Done.
184 g_screen_win_instance = nullptr;
185 }
165 186
166 // static 187 // static
167 gfx::Point ScreenWin::ScreenToDIPPoint(const gfx::Point& pixel_point) { 188 gfx::Point ScreenWin::ScreenToDIPPoint(const gfx::Point& pixel_point) {
168 return ScaleToFlooredPoint(pixel_point, 1.0f / GetDPIScale()); 189 const ScreenWinDisplay screen_win_display =
190 GetScreenWinDisplayVia(&ScreenWin::GetScreenWinDisplayNearestScreenPoint,
191 pixel_point);
192 const display::Display display = screen_win_display.display();
193 return ScalePointRelative(screen_win_display.pixel_bounds().origin(),
194 display.bounds().origin(),
195 1.0f / display.device_scale_factor(),
196 pixel_point);
169 } 197 }
170 198
171 // static 199 // static
172 gfx::Point ScreenWin::DIPToScreenPoint(const gfx::Point& dip_point) { 200 gfx::Point ScreenWin::DIPToScreenPoint(const gfx::Point& dip_point) {
173 return ScaleToFlooredPoint(dip_point, GetDPIScale()); 201 const ScreenWinDisplay screen_win_display =
202 GetScreenWinDisplayVia(&ScreenWin::GetScreenWinDisplayNearestDIPPoint,
203 dip_point);
204 const display::Display display = screen_win_display.display();
205 return ScalePointRelative(display.bounds().origin(),
206 screen_win_display.pixel_bounds().origin(),
207 display.device_scale_factor(),
208 dip_point);
174 } 209 }
175 210
176 // static 211 // static
177 gfx::Point ScreenWin::ClientToDIPPoint(HWND hwnd, 212 gfx::Point ScreenWin::ClientToDIPPoint(HWND hwnd,
178 const gfx::Point& client_point) { 213 const gfx::Point& client_point) {
179 // TODO(robliao): Get the scale factor from |hwnd|. 214 return ScaleToFlooredPoint(client_point, 1.0f / GetScaleFactorForHWND(hwnd));
180 return ScreenToDIPPoint(client_point);
181 } 215 }
182 216
183 // static 217 // static
184 gfx::Point ScreenWin::DIPToClientPoint(HWND hwnd, const gfx::Point& dip_point) { 218 gfx::Point ScreenWin::DIPToClientPoint(HWND hwnd, const gfx::Point& dip_point) {
185 // TODO(robliao): Get the scale factor from |hwnd|. 219 float scale_factor = GetScaleFactorForHWND(hwnd);
186 return DIPToScreenPoint(dip_point); 220 return ScaleToFlooredPoint(dip_point, scale_factor);
187 } 221 }
188 222
189 // static 223 // static
190 gfx::Rect ScreenWin::ScreenToDIPRect(HWND hwnd, const gfx::Rect& pixel_bounds) { 224 gfx::Rect ScreenWin::ScreenToDIPRect(HWND hwnd, const gfx::Rect& pixel_bounds) {
191 // It's important we scale the origin and size separately. If we instead 225 float scale_factor = hwnd ?
192 // calculated the size from the floored origin and ceiled right the size could 226 GetScaleFactorForHWND(hwnd) :
193 // vary depending upon where the two points land. That would cause problems 227 GetScreenWinDisplayVia(
194 // for the places this code is used (in particular mapping from native window 228 &ScreenWin::GetScreenWinDisplayNearestScreenRect, pixel_bounds).
195 // bounds to DIPs). 229 display().device_scale_factor();
196 return gfx::Rect(ScreenToDIPPoint(pixel_bounds.origin()), 230 gfx::Rect dip_rect = ScaleToEnclosingRect(pixel_bounds, 1.0f / scale_factor);
197 ScreenToDIPSize(hwnd, pixel_bounds.size())); 231 dip_rect.set_origin(ScreenToDIPPoint(pixel_bounds.origin()));
232 return dip_rect;
198 } 233 }
199 234
200 // static 235 // static
201 gfx::Rect ScreenWin::DIPToScreenRect(HWND hwnd, const gfx::Rect& dip_bounds) { 236 gfx::Rect ScreenWin::DIPToScreenRect(HWND hwnd, const gfx::Rect& dip_bounds) {
202 // See comment in ScreenToDIPRect for why we calculate size like this. 237 float scale_factor = hwnd ?
203 return gfx::Rect(DIPToScreenPoint(dip_bounds.origin()), 238 GetScaleFactorForHWND(hwnd) :
204 DIPToScreenSize(hwnd, dip_bounds.size())); 239 GetScreenWinDisplayVia(
240 &ScreenWin::GetScreenWinDisplayNearestDIPRect, dip_bounds).display().
241 device_scale_factor();
242 gfx::Rect screen_rect = ScaleToEnclosingRect(dip_bounds, scale_factor);
243 screen_rect.set_origin(DIPToScreenPoint(dip_bounds.origin()));
244 return screen_rect;
205 } 245 }
206 246
207 // static 247 // static
208 gfx::Rect ScreenWin::ClientToDIPRect(HWND hwnd, const gfx::Rect& pixel_bounds) { 248 gfx::Rect ScreenWin::ClientToDIPRect(HWND hwnd, const gfx::Rect& pixel_bounds) {
209 return ScreenToDIPRect(hwnd, pixel_bounds); 249 return ScaleToEnclosingRect(pixel_bounds, 1.0f / GetScaleFactorForHWND(hwnd));
210 } 250 }
211 251
212 // static 252 // static
213 gfx::Rect ScreenWin::DIPToClientRect(HWND hwnd, const gfx::Rect& dip_bounds) { 253 gfx::Rect ScreenWin::DIPToClientRect(HWND hwnd, const gfx::Rect& dip_bounds) {
214 return DIPToScreenRect(hwnd, dip_bounds); 254 return ScaleToEnclosingRect(dip_bounds, GetScaleFactorForHWND(hwnd));
215 } 255 }
216 256
217 // static 257 // static
218 gfx::Size ScreenWin::ScreenToDIPSize(HWND hwnd, 258 gfx::Size ScreenWin::ScreenToDIPSize(HWND hwnd,
219 const gfx::Size& size_in_pixels) { 259 const gfx::Size& size_in_pixels) {
220 // Always ceil sizes. Otherwise we may be leaving off part of the bounds. 260 // Always ceil sizes. Otherwise we may be leaving off part of the bounds.
221 // TODO(robliao): Get the scale factor from |hwnd|. 261 return ScaleToCeiledSize(size_in_pixels, 1.0f / GetScaleFactorForHWND(hwnd));
222 return ScaleToCeiledSize(size_in_pixels, 1.0f / GetDPIScale());
223 } 262 }
224 263
225 // static 264 // static
226 gfx::Size ScreenWin::DIPToScreenSize(HWND hwnd, const gfx::Size& dip_size) { 265 gfx::Size ScreenWin::DIPToScreenSize(HWND hwnd, const gfx::Size& dip_size) {
266 float scale_factor = GetScaleFactorForHWND(hwnd);
227 // Always ceil sizes. Otherwise we may be leaving off part of the bounds. 267 // Always ceil sizes. Otherwise we may be leaving off part of the bounds.
228 // TODO(robliao): Get the scale factor from |hwnd|. 268 return ScaleToCeiledSize(dip_size, scale_factor);
229 return ScaleToCeiledSize(dip_size, GetDPIScale());
230 } 269 }
231 270
232 HWND ScreenWin::GetHWNDFromNativeView(gfx::NativeView window) const { 271 HWND ScreenWin::GetHWNDFromNativeView(gfx::NativeView window) const {
233 NOTREACHED(); 272 NOTREACHED();
234 return nullptr; 273 return nullptr;
235 } 274 }
236 275
237 gfx::NativeWindow ScreenWin::GetNativeWindowFromHWND(HWND hwnd) const { 276 gfx::NativeWindow ScreenWin::GetNativeWindowFromHWND(HWND hwnd) const {
238 NOTREACHED(); 277 NOTREACHED();
239 return nullptr; 278 return nullptr;
(...skipping 59 matching lines...) Expand 10 before | Expand all | Expand 10 after
299 } 338 }
300 339
301 void ScreenWin::AddObserver(display::DisplayObserver* observer) { 340 void ScreenWin::AddObserver(display::DisplayObserver* observer) {
302 change_notifier_.AddObserver(observer); 341 change_notifier_.AddObserver(observer);
303 } 342 }
304 343
305 void ScreenWin::RemoveObserver(display::DisplayObserver* observer) { 344 void ScreenWin::RemoveObserver(display::DisplayObserver* observer) {
306 change_notifier_.RemoveObserver(observer); 345 change_notifier_.RemoveObserver(observer);
307 } 346 }
308 347
348 gfx::Rect ScreenWin::ScreenToDIPRectInWindow(
349 gfx::NativeView view, const gfx::Rect& screen_rect) const {
350 HWND hwnd = view ? GetHWNDFromNativeView(view) : nullptr;
351 return ScreenToDIPRect(hwnd, screen_rect);
352 }
353
354 gfx::Rect ScreenWin::DIPToScreenRectInWindow(gfx::NativeView view,
355 const gfx::Rect& dip_rect) const {
356 HWND hwnd = view ? GetHWNDFromNativeView(view) : nullptr;
357 return DIPToScreenRect(hwnd, dip_rect);
358 }
359
309 void ScreenWin::UpdateFromDisplayInfos( 360 void ScreenWin::UpdateFromDisplayInfos(
310 const std::vector<DisplayInfo>& display_infos) { 361 const std::vector<DisplayInfo>& display_infos) {
311 screen_win_displays_ = DisplayInfosToScreenWinDisplays(display_infos); 362 screen_win_displays_ = DisplayInfosToScreenWinDisplays(display_infos);
312 } 363 }
313 364
314 void ScreenWin::Initialize() { 365 void ScreenWin::Initialize() {
315 singleton_hwnd_observer_.reset( 366 singleton_hwnd_observer_.reset(
316 new gfx::SingletonHwndObserver( 367 new gfx::SingletonHwndObserver(
317 base::Bind(&ScreenWin::OnWndProc, base::Unretained(this)))); 368 base::Bind(&ScreenWin::OnWndProc, base::Unretained(this))));
318 UpdateFromDisplayInfos(GetDisplayInfosFromSystem()); 369 UpdateFromDisplayInfos(GetDisplayInfosFromSystem());
(...skipping 43 matching lines...) Expand 10 before | Expand all | Expand 10 after
362 ScreenWinDisplay ScreenWin::GetScreenWinDisplayNearestScreenRect( 413 ScreenWinDisplay ScreenWin::GetScreenWinDisplayNearestScreenRect(
363 const gfx::Rect& screen_rect) const { 414 const gfx::Rect& screen_rect) const {
364 return GetScreenWinDisplay(MonitorInfoFromScreenRect(screen_rect)); 415 return GetScreenWinDisplay(MonitorInfoFromScreenRect(screen_rect));
365 } 416 }
366 417
367 ScreenWinDisplay ScreenWin::GetScreenWinDisplayNearestScreenPoint( 418 ScreenWinDisplay ScreenWin::GetScreenWinDisplayNearestScreenPoint(
368 const gfx::Point& screen_point) const { 419 const gfx::Point& screen_point) const {
369 return GetScreenWinDisplay(MonitorInfoFromScreenPoint(screen_point)); 420 return GetScreenWinDisplay(MonitorInfoFromScreenPoint(screen_point));
370 } 421 }
371 422
423 ScreenWinDisplay ScreenWin::GetScreenWinDisplayNearestDIPPoint(
424 const gfx::Point& dip_point) const {
425 ScreenWinDisplay primary_screen_win_display;
426 for (const auto& screen_win_display : screen_win_displays_) {
427 display::Display display = screen_win_display.display();
428 const gfx::Rect dip_bounds = display.bounds();
429 if (dip_bounds.Contains(dip_point))
430 return screen_win_display;
431 else if (dip_bounds.origin().IsOrigin())
432 primary_screen_win_display = screen_win_display;
433 }
434 return primary_screen_win_display;
435 }
436
437 ScreenWinDisplay ScreenWin::GetScreenWinDisplayNearestDIPRect(
438 const gfx::Rect& dip_rect) const {
439 ScreenWinDisplay closest_screen_win_display;
440 int64_t closest_distance_squared = INT64_MAX;
441 for (const auto& screen_win_display : screen_win_displays_) {
442 display::Display display = screen_win_display.display();
443 gfx::Rect dip_bounds = display.bounds();
444 int64_t distance_squared = SquaredDistanceBetweenRects(dip_rect,
445 dip_bounds);
446 if (distance_squared == 0) {
447 return screen_win_display;
448 } else if (distance_squared < closest_distance_squared) {
449 closest_distance_squared = distance_squared;
450 closest_screen_win_display = screen_win_display;
451 }
452 }
453 return closest_screen_win_display;
454 }
455
372 ScreenWinDisplay ScreenWin::GetPrimaryScreenWinDisplay() const { 456 ScreenWinDisplay ScreenWin::GetPrimaryScreenWinDisplay() const {
373 MONITORINFOEX monitor_info = MonitorInfoFromWindow(nullptr, 457 MONITORINFOEX monitor_info = MonitorInfoFromWindow(nullptr,
374 MONITOR_DEFAULTTOPRIMARY); 458 MONITOR_DEFAULTTOPRIMARY);
375 ScreenWinDisplay screen_win_display = GetScreenWinDisplay(monitor_info); 459 ScreenWinDisplay screen_win_display = GetScreenWinDisplay(monitor_info);
376 display::Display display = screen_win_display.display(); 460 display::Display display = screen_win_display.display();
377 // The Windows primary monitor is defined to have an origin of (0, 0). 461 // The Windows primary monitor is defined to have an origin of (0, 0).
378 DCHECK_EQ(0, display.bounds().origin().x()); 462 DCHECK_EQ(0, display.bounds().origin().x());
379 DCHECK_EQ(0, display.bounds().origin().y()); 463 DCHECK_EQ(0, display.bounds().origin().y());
380 return screen_win_display; 464 return screen_win_display;
381 } 465 }
382 466
383 ScreenWinDisplay ScreenWin::GetScreenWinDisplay( 467 ScreenWinDisplay ScreenWin::GetScreenWinDisplay(
384 const MONITORINFOEX& monitor_info) const { 468 const MONITORINFOEX& monitor_info) const {
385 int64_t id = DisplayInfo::DeviceIdFromDeviceName(monitor_info.szDevice); 469 int64_t id = DisplayInfo::DeviceIdFromDeviceName(monitor_info.szDevice);
386 for (const auto& screen_win_display : screen_win_displays_) { 470 for (const auto& screen_win_display : screen_win_displays_) {
387 if (screen_win_display.display().id() == id) 471 if (screen_win_display.display().id() == id)
388 return screen_win_display; 472 return screen_win_display;
389 } 473 }
390 // There is 1:1 correspondence between MONITORINFOEX and ScreenWinDisplay. 474 // There is 1:1 correspondence between MONITORINFOEX and ScreenWinDisplay.
391 // If we make it here, it means we have no displays and we should hand out the 475 // If we make it here, it means we have no displays and we should hand out the
392 // default display. 476 // default display.
393 DCHECK_EQ(screen_win_displays_.size(), 0u); 477 DCHECK_EQ(screen_win_displays_.size(), 0u);
394 return ScreenWinDisplay(); 478 return ScreenWinDisplay();
395 } 479 }
396 480
481 // static
482 float ScreenWin::GetScaleFactorForHWND(HWND hwnd) {
483 if (!g_screen_win_instance)
oshima 2016/05/26 21:52:58 when/how can this happen?
robliao 2016/05/26 22:28:47 This happens in tests that don't use ScreenWin and
oshima 2016/05/26 22:54:47 Shouldn't such tests should create screen object?
robliao 2016/05/26 23:19:24 I don't think we want to force these tests to do t
oshima 2016/05/26 23:43:13 Which test is it? I want to be convinced because w
robliao 2016/05/27 00:47:13 I'll get back to you on this at the patchset that
robliao 2016/05/27 01:39:58 The ash_unittests fail: https://build.chromium.org
oshima 2016/05/27 02:02:04 The link seems to be obsolete.
robliao 2016/05/27 17:15:30 This is the full list of failures due to either th
robliao 2016/05/27 17:51:34 Since stacks aren't working, you can use this: htt
484 return ScreenWinDisplay().display().device_scale_factor();
485
486 DCHECK(hwnd);
487 HWND rootHwnd = g_screen_win_instance->GetRootWindow(hwnd);
488 ScreenWinDisplay screen_win_display =
489 g_screen_win_instance->GetScreenWinDisplayNearestHWND(rootHwnd);
490 return screen_win_display.display().device_scale_factor();
491 }
492
493 // static
494 template <typename Getter, typename GetterType>
495 ScreenWinDisplay ScreenWin::GetScreenWinDisplayVia(Getter getter,
496 GetterType value) {
497 if (!g_screen_win_instance)
498 return ScreenWinDisplay();
499
500 return (g_screen_win_instance->*getter)(value);
501 }
502
397 } // namespace win 503 } // namespace win
398 } // namespace display 504 } // namespace display
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698