15821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)// Copyright (c) 2012 The Chromium Authors. All rights reserved.
25821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)// Use of this source code is governed by a BSD-style license that can be
35821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)// found in the LICENSE file.
45821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
55821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include "ash/launcher/launcher.h"
65821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
75821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include <algorithm>
85821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include <cmath>
95821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
105821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include "ash/focus_cycler.h"
115821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include "ash/launcher/launcher_delegate.h"
125821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include "ash/launcher/launcher_model.h"
135821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include "ash/launcher/launcher_navigator.h"
145821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include "ash/launcher/launcher_view.h"
155821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include "ash/root_window_controller.h"
16c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)#include "ash/screen_ash.h"
172a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)#include "ash/shelf/shelf_layout_manager.h"
182a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)#include "ash/shelf/shelf_widget.h"
195821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include "ash/shell.h"
205821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include "ash/shell_delegate.h"
215821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include "ash/shell_window_ids.h"
222a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)#include "ash/wm/property_util.h"
235821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include "ash/wm/window_properties.h"
245821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include "grit/ash_resources.h"
252a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)#include "ui/aura/client/activation_client.h"
265821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include "ui/aura/root_window.h"
275821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include "ui/aura/window.h"
285821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include "ui/aura/window_observer.h"
295821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include "ui/base/resource/resource_bundle.h"
305821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include "ui/compositor/layer.h"
315821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include "ui/gfx/canvas.h"
325821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include "ui/gfx/image/image.h"
332a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)#include "ui/gfx/image/image_skia_operations.h"
342a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)#include "ui/gfx/skbitmap_operations.h"
355821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include "ui/views/accessible_pane_view.h"
365821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include "ui/views/widget/widget.h"
375821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include "ui/views/widget/widget_delegate.h"
385821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
395821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)namespace ash {
405821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
41c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)const char Launcher::kNativeViewName[] = "LauncherView";
42c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)
432a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)Launcher::Launcher(LauncherModel* launcher_model,
442a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)                   LauncherDelegate* launcher_delegate,
452a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)                   ShelfWidget* shelf_widget)
462a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    : launcher_view_(NULL),
472a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)      alignment_(shelf_widget->GetAlignment()),
482a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)      delegate_(launcher_delegate),
492a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)      shelf_widget_(shelf_widget) {
505821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  launcher_view_ = new internal::LauncherView(
512a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)      launcher_model, delegate_, shelf_widget_->shelf_layout_manager());
525821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  launcher_view_->Init();
532a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  shelf_widget_->GetContentsView()->AddChildView(launcher_view_);
54c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)  shelf_widget_->GetNativeView()->SetName(kNativeViewName);
55c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)  delegate_->OnLauncherCreated(this);
565821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)}
575821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
585821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)Launcher::~Launcher() {
59c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)  delegate_->OnLauncherDestroyed(this);
605821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)}
615821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
625821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)// static
635821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)Launcher* Launcher::ForPrimaryDisplay() {
6490dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)  ShelfWidget* shelf_widget = internal::RootWindowController::ForLauncher(
6590dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)      Shell::GetPrimaryRootWindow())->shelf();
6690dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)  return shelf_widget ? shelf_widget->launcher() : NULL;
675821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)}
685821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
695821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)// static
705821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)Launcher* Launcher::ForWindow(aura::Window* window) {
7190dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)  ShelfWidget* shelf_widget =
7290dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)    internal::RootWindowController::ForLauncher(window)->shelf();
7390dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)  return shelf_widget ? shelf_widget->launcher() : NULL;
745821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)}
755821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
765821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)void Launcher::SetAlignment(ShelfAlignment alignment) {
775821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  alignment_ = alignment;
782a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  launcher_view_->OnShelfAlignmentChanged();
795821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  // ShelfLayoutManager will resize the launcher.
805821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)}
815821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
825821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)gfx::Rect Launcher::GetScreenBoundsOfItemIconForWindow(aura::Window* window) {
835821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  LauncherID id = delegate_->GetIDByWindow(window);
845821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  gfx::Rect bounds(launcher_view_->GetIdealBoundsOfItemIcon(id));
855821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  gfx::Point screen_origin;
865821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  views::View::ConvertPointToScreen(launcher_view_, &screen_origin);
875821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  return gfx::Rect(screen_origin.x() + bounds.x(),
885821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                   screen_origin.y() + bounds.y(),
895821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                   bounds.width(),
905821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                   bounds.height());
915821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)}
925821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
932a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)void Launcher::UpdateIconPositionForWindow(aura::Window* window) {
942a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  launcher_view_->UpdatePanelIconPosition(
952a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)      delegate_->GetIDByWindow(window),
96c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)      ash::ScreenAsh::ConvertRectFromScreen(
97c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)          shelf_widget()->GetNativeView(),
98c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)          window->GetBoundsInScreen()).CenterPoint());
992a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)}
1002a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
1015821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)void Launcher::ActivateLauncherItem(int index) {
102c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)  // We pass in a keyboard event which will then trigger a switch to the
103c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)  // next item if the current one is already active.
104c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)  ui::KeyEvent event(ui::ET_KEY_RELEASED,
105c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)                     ui::VKEY_UNKNOWN,  // The actual key gets ignored.
106c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)                     ui::EF_NONE,
107c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)                     false);
108c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)
1092a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  const ash::LauncherItems& items =
1102a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)      launcher_view_->model()->items();
111c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)  delegate_->ItemSelected(items[index], event);
1125821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)}
1135821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1145821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)void Launcher::CycleWindowLinear(CycleDirection direction) {
1152a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  int item_index = GetNextActivatedItemIndex(
1162a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)      *(launcher_view_->model()), direction);
1175821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if (item_index >= 0)
1185821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    ActivateLauncherItem(item_index);
1195821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)}
1205821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1215821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)void Launcher::AddIconObserver(LauncherIconObserver* observer) {
1225821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  launcher_view_->AddIconObserver(observer);
1235821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)}
1245821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1255821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)void Launcher::RemoveIconObserver(LauncherIconObserver* observer) {
1265821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  launcher_view_->RemoveIconObserver(observer);
1275821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)}
1285821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1295821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)bool Launcher::IsShowingMenu() const {
1305821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  return launcher_view_->IsShowingMenu();
1315821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)}
1325821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1335821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)bool Launcher::IsShowingOverflowBubble() const {
1345821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  return launcher_view_->IsShowingOverflowBubble();
1355821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)}
1365821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1375821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)void Launcher::SetVisible(bool visible) const {
1382a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  launcher_view_->SetVisible(visible);
1392a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)}
1402a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
1412a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)bool Launcher::IsVisible() const {
1422a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  return launcher_view_->visible();
1435821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)}
1445821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1454311e82a78ceafbe0585f51d4c8a86df9f21aa0dBen Murdochvoid Launcher::SchedulePaint() {
1464311e82a78ceafbe0585f51d4c8a86df9f21aa0dBen Murdoch  launcher_view_->SchedulePaintForAllButtons();
1474311e82a78ceafbe0585f51d4c8a86df9f21aa0dBen Murdoch}
1484311e82a78ceafbe0585f51d4c8a86df9f21aa0dBen Murdoch
1495821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)views::View* Launcher::GetAppListButtonView() const {
1505821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  return launcher_view_->GetAppListButtonView();
1515821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)}
1525821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
153eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdochvoid Launcher::LaunchAppIndexAt(int item_index) {
1542a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  LauncherModel* launcher_model = launcher_view_->model();
1552a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  const LauncherItems& items = launcher_model->items();
1562a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  int item_count = launcher_model->item_count();
157eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch  int indexes_left = item_index >= 0 ? item_index : item_count;
1582a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  int found_index = -1;
1592a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
1602a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  // Iterating until we have hit the index we are interested in which
1612a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  // is true once indexes_left becomes negative.
1622a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  for (int i = 0; i < item_count && indexes_left >= 0; i++) {
16390dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles)    if (items[i].type != TYPE_APP_LIST) {
1642a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)      found_index = i;
1652a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)      indexes_left--;
1662a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    }
1672a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  }
1682a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
1692a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  // There are two ways how found_index can be valid: a.) the nth item was
1702a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  // found (which is true when indexes_left is -1) or b.) the last item was
1712a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  // requested (which is true when index was passed in as a negative number).
172eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch  if (found_index >= 0 && (indexes_left == -1 || item_index < 0) &&
173c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)      (delegate_->IsPerAppLauncher() ||
174c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)       (items[found_index].status == ash::STATUS_RUNNING ||
175c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)        items[found_index].status == ash::STATUS_CLOSED))) {
176c2e0dbddbe15c98d52c4786dac06cb8952a8ae6dTorne (Richard Coles)    // Then set this one as active (or advance to the next item of its kind).
1772a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    ActivateLauncherItem(found_index);
1782a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  }
1795821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)}
1805821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1815821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)internal::LauncherView* Launcher::GetLauncherViewForTest() {
1825821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  return launcher_view_;
1835821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)}
1845821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1852a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)void Launcher::SetLauncherViewBounds(gfx::Rect bounds) {
1862a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  launcher_view_->SetBoundsRect(bounds);
1872a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)}
1882a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
1892a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)gfx::Rect Launcher::GetLauncherViewBounds() const {
1902a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  return launcher_view_->bounds();
1912a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)}
1922a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
193bb1529ce867d8845a77ec7cdf3e3003ef1771a40Ben Murdochapp_list::ApplicationDragAndDropHost* Launcher::GetDragAndDropHostForAppList() {
194bb1529ce867d8845a77ec7cdf3e3003ef1771a40Ben Murdoch  return launcher_view_;
195bb1529ce867d8845a77ec7cdf3e3003ef1771a40Ben Murdoch}
196bb1529ce867d8845a77ec7cdf3e3003ef1771a40Ben Murdoch
1975821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)}  // namespace ash
198