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

Side by Side Diff: ash/shelf/shelf_view.cc

Issue 2791463002: mash: Remove ShelfDelegate; move functions to ShelfModel. (Closed)
Patch Set: Sync and rebase; cleanup. Created 3 years, 8 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 "ash/shelf/shelf_view.h" 5 #include "ash/shelf/shelf_view.h"
6 6
7 #include <algorithm> 7 #include <algorithm>
8 #include <memory> 8 #include <memory>
9 9
10 #include "ash/ash_constants.h" 10 #include "ash/ash_constants.h"
11 #include "ash/drag_drop/drag_image_view.h" 11 #include "ash/drag_drop/drag_image_view.h"
12 #include "ash/public/cpp/shelf_item_delegate.h" 12 #include "ash/public/cpp/shelf_item_delegate.h"
13 #include "ash/scoped_root_window_for_new_windows.h" 13 #include "ash/scoped_root_window_for_new_windows.h"
14 #include "ash/shelf/app_list_button.h" 14 #include "ash/shelf/app_list_button.h"
15 #include "ash/shelf/overflow_bubble.h" 15 #include "ash/shelf/overflow_bubble.h"
16 #include "ash/shelf/overflow_bubble_view.h" 16 #include "ash/shelf/overflow_bubble_view.h"
17 #include "ash/shelf/overflow_button.h" 17 #include "ash/shelf/overflow_button.h"
18 #include "ash/shelf/shelf_application_menu_model.h" 18 #include "ash/shelf/shelf_application_menu_model.h"
19 #include "ash/shelf/shelf_button.h" 19 #include "ash/shelf/shelf_button.h"
20 #include "ash/shelf/shelf_constants.h" 20 #include "ash/shelf/shelf_constants.h"
21 #include "ash/shelf/shelf_delegate.h"
22 #include "ash/shelf/shelf_model.h" 21 #include "ash/shelf/shelf_model.h"
23 #include "ash/shelf/shelf_widget.h" 22 #include "ash/shelf/shelf_widget.h"
24 #include "ash/shelf/wm_shelf.h" 23 #include "ash/shelf/wm_shelf.h"
25 #include "ash/shell.h" 24 #include "ash/shell.h"
26 #include "ash/shell_delegate.h" 25 #include "ash/shell_delegate.h"
27 #include "ash/shell_port.h" 26 #include "ash/shell_port.h"
28 #include "ash/strings/grit/ash_strings.h" 27 #include "ash/strings/grit/ash_strings.h"
29 #include "ash/wm/root_window_finder.h" 28 #include "ash/wm/root_window_finder.h"
30 #include "ash/wm_window.h" 29 #include "ash/wm_window.h"
31 #include "base/auto_reset.h" 30 #include "base/auto_reset.h"
(...skipping 200 matching lines...) Expand 10 before | Expand all | Expand 10 after
232 ShelfView* shelf_view_; 231 ShelfView* shelf_view_;
233 views::View* view_; 232 views::View* view_;
234 233
235 DISALLOW_COPY_AND_ASSIGN(StartFadeAnimationDelegate); 234 DISALLOW_COPY_AND_ASSIGN(StartFadeAnimationDelegate);
236 }; 235 };
237 236
238 // static 237 // static
239 const int ShelfView::kMinimumDragDistance = 8; 238 const int ShelfView::kMinimumDragDistance = 8;
240 239
241 ShelfView::ShelfView(ShelfModel* model, 240 ShelfView::ShelfView(ShelfModel* model,
242 ShelfDelegate* delegate,
243 WmShelf* wm_shelf, 241 WmShelf* wm_shelf,
244 ShelfWidget* shelf_widget) 242 ShelfWidget* shelf_widget)
245 : model_(model), 243 : model_(model),
246 delegate_(delegate),
247 wm_shelf_(wm_shelf), 244 wm_shelf_(wm_shelf),
248 shelf_widget_(shelf_widget), 245 shelf_widget_(shelf_widget),
249 view_model_(new views::ViewModel), 246 view_model_(new views::ViewModel),
250 first_visible_index_(0), 247 first_visible_index_(0),
251 last_visible_index_(-1), 248 last_visible_index_(-1),
252 overflow_button_(nullptr), 249 overflow_button_(nullptr),
253 owner_overflow_bubble_(nullptr), 250 owner_overflow_bubble_(nullptr),
254 tooltip_(this), 251 tooltip_(this),
255 drag_pointer_(NONE), 252 drag_pointer_(NONE),
256 drag_view_(nullptr), 253 drag_view_(nullptr),
257 start_drag_index_(-1), 254 start_drag_index_(-1),
258 context_menu_id_(0), 255 context_menu_id_(0),
259 cancelling_drag_model_changed_(false), 256 cancelling_drag_model_changed_(false),
260 last_hidden_index_(0), 257 last_hidden_index_(0),
261 closing_event_time_(base::TimeTicks()), 258 closing_event_time_(base::TimeTicks()),
262 drag_and_drop_item_pinned_(false), 259 drag_and_drop_item_pinned_(false),
263 drag_and_drop_shelf_id_(0), 260 drag_and_drop_shelf_id_(0),
264 drag_replaced_view_(nullptr), 261 drag_replaced_view_(nullptr),
265 dragged_off_shelf_(false), 262 dragged_off_shelf_(false),
266 snap_back_from_rip_off_view_(nullptr), 263 snap_back_from_rip_off_view_(nullptr),
267 overflow_mode_(false), 264 overflow_mode_(false),
268 main_shelf_(nullptr), 265 main_shelf_(nullptr),
269 dragged_off_from_overflow_to_shelf_(false), 266 dragged_off_from_overflow_to_shelf_(false),
270 is_repost_event_on_same_item_(false), 267 is_repost_event_on_same_item_(false),
271 last_pressed_index_(-1), 268 last_pressed_index_(-1),
272 weak_factory_(this) { 269 weak_factory_(this) {
273 DCHECK(model_); 270 DCHECK(model_);
274 DCHECK(delegate_);
275 DCHECK(wm_shelf_); 271 DCHECK(wm_shelf_);
276 DCHECK(shelf_widget_); 272 DCHECK(shelf_widget_);
277 bounds_animator_.reset(new views::BoundsAnimator(this)); 273 bounds_animator_.reset(new views::BoundsAnimator(this));
278 bounds_animator_->AddObserver(this); 274 bounds_animator_->AddObserver(this);
279 set_context_menu_controller(this); 275 set_context_menu_controller(this);
280 focus_search_.reset(new ShelfFocusSearch(view_model_.get())); 276 focus_search_.reset(new ShelfFocusSearch(view_model_.get()));
281 } 277 }
282 278
283 ShelfView::~ShelfView() { 279 ShelfView::~ShelfView() {
284 bounds_animator_->RemoveObserver(this); 280 bounds_animator_->RemoveObserver(this);
(...skipping 269 matching lines...) Expand 10 before | Expand all | Expand 10 after
554 // This could happen if mouse / touch operations overlap. 550 // This could happen if mouse / touch operations overlap.
555 if (drag_and_drop_shelf_id_ || 551 if (drag_and_drop_shelf_id_ ||
556 !GetBoundsInScreen().Contains(location_in_screen_coordinates)) 552 !GetBoundsInScreen().Contains(location_in_screen_coordinates))
557 return false; 553 return false;
558 554
559 // If the AppsGridView (which was dispatching this event) was opened by our 555 // If the AppsGridView (which was dispatching this event) was opened by our
560 // button, ShelfView dragging operations are locked and we have to unlock. 556 // button, ShelfView dragging operations are locked and we have to unlock.
561 CancelDrag(-1); 557 CancelDrag(-1);
562 drag_and_drop_item_pinned_ = false; 558 drag_and_drop_item_pinned_ = false;
563 drag_and_drop_app_id_ = app_id; 559 drag_and_drop_app_id_ = app_id;
564 drag_and_drop_shelf_id_ = 560 drag_and_drop_shelf_id_ = model_->GetShelfIDForAppID(drag_and_drop_app_id_);
565 delegate_->GetShelfIDForAppID(drag_and_drop_app_id_);
566 // Check if the application is known and pinned - if not, we have to pin it so 561 // Check if the application is known and pinned - if not, we have to pin it so
567 // that we can re-arrange the shelf order accordingly. Note that items have 562 // that we can re-arrange the shelf order accordingly. Note that items have
568 // to be pinned to give them the same (order) possibilities as a shortcut. 563 // to be pinned to give them the same (order) possibilities as a shortcut.
569 // When an item is dragged from overflow to shelf, IsShowingOverflowBubble() 564 // When an item is dragged from overflow to shelf, IsShowingOverflowBubble()
570 // returns true. At this time, we don't need to pin the item. 565 // returns true. At this time, we don't need to pin the item.
571 if (!IsShowingOverflowBubble() && 566 if (!IsShowingOverflowBubble() &&
572 (!drag_and_drop_shelf_id_ || !delegate_->IsAppPinned(app_id))) { 567 (!drag_and_drop_shelf_id_ || !model_->IsAppPinned(app_id))) {
573 delegate_->PinAppWithID(app_id); 568 model_->PinAppWithID(app_id);
574 drag_and_drop_shelf_id_ = 569 drag_and_drop_shelf_id_ = model_->GetShelfIDForAppID(drag_and_drop_app_id_);
575 delegate_->GetShelfIDForAppID(drag_and_drop_app_id_);
576 if (!drag_and_drop_shelf_id_) 570 if (!drag_and_drop_shelf_id_)
577 return false; 571 return false;
578 drag_and_drop_item_pinned_ = true; 572 drag_and_drop_item_pinned_ = true;
579 } 573 }
580 views::View* drag_and_drop_view = 574 views::View* drag_and_drop_view =
581 view_model_->view_at(model_->ItemIndexByID(drag_and_drop_shelf_id_)); 575 view_model_->view_at(model_->ItemIndexByID(drag_and_drop_shelf_id_));
582 DCHECK(drag_and_drop_view); 576 DCHECK(drag_and_drop_view);
583 577
584 // Since there is already an icon presented by the caller, we hide this item 578 // Since there is already an icon presented by the caller, we hide this item
585 // for now. That has to be done by reducing the size since the visibility will 579 // for now. That has to be done by reducing the size since the visibility will
(...skipping 37 matching lines...) Expand 10 before | Expand all | Expand 10 after
623 void ShelfView::EndDrag(bool cancel) { 617 void ShelfView::EndDrag(bool cancel) {
624 if (!drag_and_drop_shelf_id_) 618 if (!drag_and_drop_shelf_id_)
625 return; 619 return;
626 620
627 views::View* drag_and_drop_view = 621 views::View* drag_and_drop_view =
628 view_model_->view_at(model_->ItemIndexByID(drag_and_drop_shelf_id_)); 622 view_model_->view_at(model_->ItemIndexByID(drag_and_drop_shelf_id_));
629 PointerReleasedOnButton(drag_and_drop_view, DRAG_AND_DROP, cancel); 623 PointerReleasedOnButton(drag_and_drop_view, DRAG_AND_DROP, cancel);
630 624
631 // Either destroy the temporarily created item - or - make the item visible. 625 // Either destroy the temporarily created item - or - make the item visible.
632 if (drag_and_drop_item_pinned_ && cancel) { 626 if (drag_and_drop_item_pinned_ && cancel) {
633 delegate_->UnpinAppWithID(drag_and_drop_app_id_); 627 model_->UnpinAppWithID(drag_and_drop_app_id_);
634 } else if (drag_and_drop_view) { 628 } else if (drag_and_drop_view) {
635 if (cancel) { 629 if (cancel) {
636 // When a hosted drag gets canceled, the item can remain in the same slot 630 // When a hosted drag gets canceled, the item can remain in the same slot
637 // and it might have moved within the bounds. In that case the item need 631 // and it might have moved within the bounds. In that case the item need
638 // to animate back to its correct location. 632 // to animate back to its correct location.
639 AnimateToIdealBounds(); 633 AnimateToIdealBounds();
640 } else { 634 } else {
641 drag_and_drop_view->SetSize(pre_drag_and_drop_size_); 635 drag_and_drop_view->SetSize(pre_drag_and_drop_size_);
642 } 636 }
643 } 637 }
(...skipping 413 matching lines...) Expand 10 before | Expand all | Expand 10 after
1057 // Change the model, the ShelfItemMoved() callback will handle the 1051 // Change the model, the ShelfItemMoved() callback will handle the
1058 // |view_model_| update. 1052 // |view_model_| update.
1059 model_->Move(current_index, target_index); 1053 model_->Move(current_index, target_index);
1060 bounds_animator_->StopAnimatingView(drag_view_); 1054 bounds_animator_->StopAnimatingView(drag_view_);
1061 } 1055 }
1062 1056
1063 bool ShelfView::HandleRipOffDrag(const ui::LocatedEvent& event) { 1057 bool ShelfView::HandleRipOffDrag(const ui::LocatedEvent& event) {
1064 int current_index = view_model_->GetIndexOfView(drag_view_); 1058 int current_index = view_model_->GetIndexOfView(drag_view_);
1065 DCHECK_NE(-1, current_index); 1059 DCHECK_NE(-1, current_index);
1066 std::string dragged_app_id = 1060 std::string dragged_app_id =
1067 delegate_->GetAppIDForShelfID(model_->items()[current_index].id); 1061 model_->GetAppIDForShelfID(model_->items()[current_index].id);
1068 1062
1069 gfx::Point screen_location = 1063 gfx::Point screen_location =
1070 WmWindow::Get(GetWidget()->GetNativeWindow()) 1064 WmWindow::Get(GetWidget()->GetNativeWindow())
1071 ->GetRootWindow() 1065 ->GetRootWindow()
1072 ->ConvertPointToScreen(event.root_location()); 1066 ->ConvertPointToScreen(event.root_location());
1073 1067
1074 // To avoid ugly forwards and backwards flipping we use different constants 1068 // To avoid ugly forwards and backwards flipping we use different constants
1075 // for ripping off / re-inserting the items. 1069 // for ripping off / re-inserting the items.
1076 if (dragged_off_shelf_) { 1070 if (dragged_off_shelf_) {
1077 // If the shelf/overflow bubble bounds contains |screen_location| we insert 1071 // If the shelf/overflow bubble bounds contains |screen_location| we insert
(...skipping 102 matching lines...) Expand 10 before | Expand all | Expand 10 after
1180 drag_view_->layer()->SetOpacity(1.0f); 1174 drag_view_->layer()->SetOpacity(1.0f);
1181 } else if (RemovableByRipOff(current_index) != REMOVABLE) { 1175 } else if (RemovableByRipOff(current_index) != REMOVABLE) {
1182 // Make sure we do not try to remove un-removable items like items which 1176 // Make sure we do not try to remove un-removable items like items which
1183 // were not pinned or have to be always there. 1177 // were not pinned or have to be always there.
1184 cancel = true; 1178 cancel = true;
1185 snap_back = true; 1179 snap_back = true;
1186 } else { 1180 } else {
1187 // Make sure the item stays invisible upon removal. 1181 // Make sure the item stays invisible upon removal.
1188 drag_view_->SetVisible(false); 1182 drag_view_->SetVisible(false);
1189 std::string app_id = 1183 std::string app_id =
1190 delegate_->GetAppIDForShelfID(model_->items()[current_index].id); 1184 model_->GetAppIDForShelfID(model_->items()[current_index].id);
1191 delegate_->UnpinAppWithID(app_id); 1185 model_->UnpinAppWithID(app_id);
1192 } 1186 }
1193 } 1187 }
1194 if (cancel || snap_back) { 1188 if (cancel || snap_back) {
1195 if (dragged_off_from_overflow_to_shelf_) { 1189 if (dragged_off_from_overflow_to_shelf_) {
1196 dragged_off_from_overflow_to_shelf_ = false; 1190 dragged_off_from_overflow_to_shelf_ = false;
1197 // Main shelf handles revert of dragged item. 1191 // Main shelf handles revert of dragged item.
1198 main_shelf_->EndDrag(true); 1192 main_shelf_->EndDrag(true);
1199 drag_view_->layer()->SetOpacity(1.0f); 1193 drag_view_->layer()->SetOpacity(1.0f);
1200 } else if (!cancelling_drag_model_changed_) { 1194 } else if (!cancelling_drag_model_changed_) {
1201 // Only do something if the change did not come through a model change. 1195 // Only do something if the change did not come through a model change.
(...skipping 21 matching lines...) Expand all
1223 ShelfView::RemovableState ShelfView::RemovableByRipOff(int index) const { 1217 ShelfView::RemovableState ShelfView::RemovableByRipOff(int index) const {
1224 DCHECK(index >= 0 && index < model_->item_count()); 1218 DCHECK(index >= 0 && index < model_->item_count());
1225 ShelfItemType type = model_->items()[index].type; 1219 ShelfItemType type = model_->items()[index].type;
1226 if (type == TYPE_APP_LIST || type == TYPE_DIALOG) 1220 if (type == TYPE_APP_LIST || type == TYPE_DIALOG)
1227 return NOT_REMOVABLE; 1221 return NOT_REMOVABLE;
1228 1222
1229 if (model_->items()[index].pinned_by_policy) 1223 if (model_->items()[index].pinned_by_policy)
1230 return NOT_REMOVABLE; 1224 return NOT_REMOVABLE;
1231 1225
1232 // Note: Only pinned app shortcuts can be removed! 1226 // Note: Only pinned app shortcuts can be removed!
1233 std::string app_id = delegate_->GetAppIDForShelfID(model_->items()[index].id); 1227 std::string app_id = model_->GetAppIDForShelfID(model_->items()[index].id);
1234 return (type == TYPE_PINNED_APP && delegate_->IsAppPinned(app_id)) 1228 return (type == TYPE_PINNED_APP && model_->IsAppPinned(app_id)) ? REMOVABLE
1235 ? REMOVABLE 1229 : DRAGGABLE;
1236 : DRAGGABLE;
1237 } 1230 }
1238 1231
1239 bool ShelfView::SameDragType(ShelfItemType typea, ShelfItemType typeb) const { 1232 bool ShelfView::SameDragType(ShelfItemType typea, ShelfItemType typeb) const {
1240 switch (typea) { 1233 switch (typea) {
1241 case TYPE_PINNED_APP: 1234 case TYPE_PINNED_APP:
1242 case TYPE_BROWSER_SHORTCUT: 1235 case TYPE_BROWSER_SHORTCUT:
1243 return (typeb == TYPE_PINNED_APP || typeb == TYPE_BROWSER_SHORTCUT); 1236 return (typeb == TYPE_PINNED_APP || typeb == TYPE_BROWSER_SHORTCUT);
1244 case TYPE_APP_PANEL: 1237 case TYPE_APP_PANEL:
1245 case TYPE_APP_LIST: 1238 case TYPE_APP_LIST:
1246 case TYPE_APP: 1239 case TYPE_APP:
(...skipping 28 matching lines...) Expand all
1275 1268
1276 void ShelfView::ToggleOverflowBubble() { 1269 void ShelfView::ToggleOverflowBubble() {
1277 if (IsShowingOverflowBubble()) { 1270 if (IsShowingOverflowBubble()) {
1278 overflow_bubble_->Hide(); 1271 overflow_bubble_->Hide();
1279 return; 1272 return;
1280 } 1273 }
1281 1274
1282 if (!overflow_bubble_) 1275 if (!overflow_bubble_)
1283 overflow_bubble_.reset(new OverflowBubble(wm_shelf_)); 1276 overflow_bubble_.reset(new OverflowBubble(wm_shelf_));
1284 1277
1285 ShelfView* overflow_view = 1278 ShelfView* overflow_view = new ShelfView(model_, wm_shelf_, shelf_widget_);
1286 new ShelfView(model_, delegate_, wm_shelf_, shelf_widget_);
1287 overflow_view->overflow_mode_ = true; 1279 overflow_view->overflow_mode_ = true;
1288 overflow_view->Init(); 1280 overflow_view->Init();
1289 overflow_view->set_owner_overflow_bubble(overflow_bubble_.get()); 1281 overflow_view->set_owner_overflow_bubble(overflow_bubble_.get());
1290 overflow_view->OnShelfAlignmentChanged(); 1282 overflow_view->OnShelfAlignmentChanged();
1291 overflow_view->main_shelf_ = this; 1283 overflow_view->main_shelf_ = this;
1292 UpdateOverflowRange(overflow_view); 1284 UpdateOverflowRange(overflow_view);
1293 1285
1294 overflow_bubble_->Show(overflow_button_, overflow_view); 1286 overflow_bubble_->Show(overflow_button_, overflow_view);
1295 1287
1296 wm_shelf_->UpdateVisibilityState(); 1288 wm_shelf_->UpdateVisibilityState();
(...skipping 235 matching lines...) Expand 10 before | Expand all | Expand 10 after
1532 // item is ripped out from the shelf, its |view| is already invisible. 1524 // item is ripped out from the shelf, its |view| is already invisible.
1533 AnimateToIdealBounds(); 1525 AnimateToIdealBounds();
1534 } 1526 }
1535 1527
1536 if (view == tooltip_.GetCurrentAnchorView()) 1528 if (view == tooltip_.GetCurrentAnchorView())
1537 tooltip_.Close(); 1529 tooltip_.Close();
1538 } 1530 }
1539 1531
1540 void ShelfView::ShelfItemChanged(int model_index, const ShelfItem& old_item) { 1532 void ShelfView::ShelfItemChanged(int model_index, const ShelfItem& old_item) {
1541 const ShelfItem& item(model_->items()[model_index]); 1533 const ShelfItem& item(model_->items()[model_index]);
1534
1535 // Bail if the view and shelf sizes do not match. ShelfItemChanged may be
1536 // called here before ShelfItemAdded, due to ChromeLauncherController's
1537 // item initialization, which calls SetItem during ShelfItemAdded.
1538 if (static_cast<int>(model_->items().size()) != view_model_->view_size())
James Cook 2017/04/19 00:23:47 Are you sure this is safe? Is it only for tests or
msw 2017/04/19 20:56:06 This is used for production and I'm reasonably con
1539 return;
1540
1542 if (old_item.type != item.type) { 1541 if (old_item.type != item.type) {
1543 // Type changed, swap the views. 1542 // Type changed, swap the views.
1544 model_index = CancelDrag(model_index); 1543 model_index = CancelDrag(model_index);
1545 std::unique_ptr<views::View> old_view(view_model_->view_at(model_index)); 1544 std::unique_ptr<views::View> old_view(view_model_->view_at(model_index));
1546 bounds_animator_->StopAnimatingView(old_view.get()); 1545 bounds_animator_->StopAnimatingView(old_view.get());
1547 // Removing and re-inserting a view in our view model will strip the ideal 1546 // Removing and re-inserting a view in our view model will strip the ideal
1548 // bounds from the item. To avoid recalculation of everything the bounds 1547 // bounds from the item. To avoid recalculation of everything the bounds
1549 // get remembered and restored after the insertion to the previous value. 1548 // get remembered and restored after the insertion to the previous value.
1550 gfx::Rect old_ideal_bounds = view_model_->ideal_bounds(model_index); 1549 gfx::Rect old_ideal_bounds = view_model_->ideal_bounds(model_index);
1551 view_model_->Remove(model_index); 1550 view_model_->Remove(model_index);
(...skipping 210 matching lines...) Expand 10 before | Expand all | Expand 10 after
1762 1761
1763 int ShelfView::CalculateShelfDistance(const gfx::Point& coordinate) const { 1762 int ShelfView::CalculateShelfDistance(const gfx::Point& coordinate) const {
1764 const gfx::Rect bounds = GetBoundsInScreen(); 1763 const gfx::Rect bounds = GetBoundsInScreen();
1765 int distance = wm_shelf_->SelectValueForShelfAlignment( 1764 int distance = wm_shelf_->SelectValueForShelfAlignment(
1766 bounds.y() - coordinate.y(), coordinate.x() - bounds.right(), 1765 bounds.y() - coordinate.y(), coordinate.x() - bounds.right(),
1767 bounds.x() - coordinate.x()); 1766 bounds.x() - coordinate.x());
1768 return distance > 0 ? distance : 0; 1767 return distance > 0 ? distance : 0;
1769 } 1768 }
1770 1769
1771 } // namespace ash 1770 } // namespace ash
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698