browser_non_client_frame_view_ash.cc revision f8ee788a64d60abd8f2d742a5fdedde054ecd910
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 3// found in the LICENSE file. 4 5#include "chrome/browser/ui/views/frame/browser_non_client_frame_view_ash.h" 6 7#include <algorithm> 8 9#include "ash/ash_switches.h" 10#include "ash/frame/caption_buttons/frame_caption_button_container_view.h" 11#include "ash/frame/default_header_painter.h" 12#include "ash/frame/frame_border_hit_test_controller.h" 13#include "ash/frame/header_painter_util.h" 14#include "ash/shell.h" 15#include "base/command_line.h" 16#include "chrome/browser/themes/theme_properties.h" 17#include "chrome/browser/ui/browser.h" 18#include "chrome/browser/ui/views/frame/browser_frame.h" 19#include "chrome/browser/ui/views/frame/browser_header_painter_ash.h" 20#include "chrome/browser/ui/views/frame/browser_view.h" 21#include "chrome/browser/ui/views/frame/immersive_mode_controller.h" 22#include "chrome/browser/ui/views/profiles/avatar_label.h" 23#include "chrome/browser/ui/views/profiles/avatar_menu_button.h" 24#include "chrome/browser/ui/views/tab_icon_view.h" 25#include "chrome/browser/ui/views/tabs/tab_strip.h" 26#include "chrome/common/chrome_switches.h" 27#include "content/public/browser/web_contents.h" 28#include "grit/ash_resources.h" 29#include "grit/theme_resources.h" 30#include "ui/accessibility/ax_view_state.h" 31#include "ui/aura/client/aura_constants.h" 32#include "ui/aura/window.h" 33#include "ui/base/hit_test.h" 34#include "ui/base/l10n/l10n_util.h" 35#include "ui/base/layout.h" 36#include "ui/base/resource/resource_bundle.h" 37#include "ui/base/theme_provider.h" 38#include "ui/compositor/layer_animator.h" 39#include "ui/gfx/canvas.h" 40#include "ui/gfx/image/image_skia.h" 41#include "ui/gfx/rect_conversions.h" 42#include "ui/views/controls/label.h" 43#include "ui/views/layout/layout_constants.h" 44#include "ui/views/widget/widget.h" 45#include "ui/views/widget/widget_delegate.h" 46 47namespace { 48 49// The avatar ends 2 px above the bottom of the tabstrip (which, given the 50// way the tabstrip draws its bottom edge, will appear like a 1 px gap to the 51// user). 52const int kAvatarBottomSpacing = 2; 53// There are 2 px on each side of the avatar (between the frame border and 54// it on the left, and between it and the tabstrip on the right). 55const int kAvatarSideSpacing = 2; 56// Space between left edge of window and tabstrip. 57const int kTabstripLeftSpacing = 0; 58// Space between right edge of tabstrip and maximize button. 59const int kTabstripRightSpacing = 10; 60// Height of the shadow of the content area, at the top of the toolbar. 61const int kContentShadowHeight = 1; 62// Space between top of window and top of tabstrip for tall headers, such as 63// for restored windows, apps, etc. 64const int kTabstripTopSpacingTall = 7; 65// Space between top of window and top of tabstrip for short headers, such as 66// for maximized windows, pop-ups, etc. 67const int kTabstripTopSpacingShort = 0; 68// Height of the shadow in the tab image, used to ensure clicks in the shadow 69// area still drag restored windows. This keeps the clickable area large enough 70// to hit easily. 71const int kTabShadowHeight = 4; 72 73} // namespace 74 75/////////////////////////////////////////////////////////////////////////////// 76// BrowserNonClientFrameViewAsh, public: 77 78// static 79const char BrowserNonClientFrameViewAsh::kViewClassName[] = 80 "BrowserNonClientFrameViewAsh"; 81 82BrowserNonClientFrameViewAsh::BrowserNonClientFrameViewAsh( 83 BrowserFrame* frame, BrowserView* browser_view) 84 : BrowserNonClientFrameView(frame, browser_view), 85 caption_button_container_(NULL), 86 window_icon_(NULL), 87 frame_border_hit_test_controller_( 88 new ash::FrameBorderHitTestController(frame)) { 89 ash::Shell::GetInstance()->AddShellObserver(this); 90} 91 92BrowserNonClientFrameViewAsh::~BrowserNonClientFrameViewAsh() { 93 ash::Shell::GetInstance()->RemoveShellObserver(this); 94} 95 96void BrowserNonClientFrameViewAsh::Init() { 97 caption_button_container_ = new ash::FrameCaptionButtonContainerView(frame(), 98 ash::FrameCaptionButtonContainerView::MINIMIZE_ALLOWED); 99 caption_button_container_->UpdateSizeButtonVisibility(); 100 AddChildView(caption_button_container_); 101 102 // Initializing the TabIconView is expensive, so only do it if we need to. 103 if (browser_view()->ShouldShowWindowIcon()) { 104 window_icon_ = new TabIconView(this, NULL); 105 window_icon_->set_is_light(true); 106 AddChildView(window_icon_); 107 window_icon_->Update(); 108 } 109 110 // Create incognito icon if necessary. 111 UpdateAvatarInfo(); 112 113 // HeaderPainter handles layout. 114 if (UsePackagedAppHeaderStyle()) { 115 ash::DefaultHeaderPainter* header_painter = new ash::DefaultHeaderPainter; 116 header_painter_.reset(header_painter); 117 header_painter->Init(frame(), this, window_icon_, 118 caption_button_container_); 119 } else { 120 BrowserHeaderPainterAsh* header_painter = new BrowserHeaderPainterAsh; 121 header_painter_.reset(header_painter); 122 header_painter->Init(frame(), browser_view(), this, window_icon_, 123 caption_button_container_); 124 } 125} 126 127/////////////////////////////////////////////////////////////////////////////// 128// BrowserNonClientFrameView: 129 130gfx::Rect BrowserNonClientFrameViewAsh::GetBoundsForTabStrip( 131 views::View* tabstrip) const { 132 if (!tabstrip) 133 return gfx::Rect(); 134 135 // When the tab strip is painted in the immersive fullscreen light bar style, 136 // the caption buttons and the avatar button are not visible. However, their 137 // bounds are still used to compute the tab strip bounds so that the tabs have 138 // the same horizontal position when the tab strip is painted in the immersive 139 // light bar style as when the top-of-window views are revealed. 140 int left_inset = GetTabStripLeftInset(); 141 int right_inset = GetTabStripRightInset(); 142 return gfx::Rect(left_inset, 143 GetTopInset(), 144 std::max(0, width() - left_inset - right_inset), 145 tabstrip->GetPreferredSize().height()); 146} 147 148int BrowserNonClientFrameViewAsh::GetTopInset() const { 149 if (!ShouldPaint() || UseImmersiveLightbarHeaderStyle()) 150 return 0; 151 152 if (browser_view()->IsTabStripVisible()) { 153 if (frame()->IsMaximized() || frame()->IsFullscreen()) 154 return kTabstripTopSpacingShort; 155 else 156 return kTabstripTopSpacingTall; 157 } 158 159 if (UsePackagedAppHeaderStyle()) 160 return header_painter_->GetHeaderHeightForPainting(); 161 162 int caption_buttons_bottom = caption_button_container_->bounds().bottom(); 163 164 // The toolbar partially overlaps the caption buttons. 165 if (browser_view()->IsToolbarVisible()) 166 return caption_buttons_bottom - kContentShadowHeight; 167 168 return caption_buttons_bottom + kClientEdgeThickness; 169} 170 171int BrowserNonClientFrameViewAsh::GetThemeBackgroundXInset() const { 172 return ash::HeaderPainterUtil::GetThemeBackgroundXInset(); 173} 174 175void BrowserNonClientFrameViewAsh::UpdateThrobber(bool running) { 176 if (window_icon_) 177 window_icon_->Update(); 178} 179 180/////////////////////////////////////////////////////////////////////////////// 181// views::NonClientFrameView: 182 183gfx::Rect BrowserNonClientFrameViewAsh::GetBoundsForClientView() const { 184 // The ClientView must be flush with the top edge of the widget so that the 185 // web contents can take up the entire screen in immersive fullscreen (with 186 // or without the top-of-window views revealed). When in immersive fullscreen 187 // and the top-of-window views are revealed, the TopContainerView paints the 188 // window header by redirecting paints from its background to 189 // BrowserNonClientFrameViewAsh. 190 return bounds(); 191} 192 193gfx::Rect BrowserNonClientFrameViewAsh::GetWindowBoundsForClientBounds( 194 const gfx::Rect& client_bounds) const { 195 return client_bounds; 196} 197 198int BrowserNonClientFrameViewAsh::NonClientHitTest(const gfx::Point& point) { 199 int hit_test = ash::FrameBorderHitTestController::NonClientHitTest(this, 200 caption_button_container_, point); 201 202 // See if the point is actually within the avatar menu button or within 203 // the avatar label. 204 if (hit_test == HTCAPTION && ((avatar_button() && 205 avatar_button()->GetMirroredBounds().Contains(point)) || 206 (avatar_label() && avatar_label()->GetMirroredBounds().Contains(point)))) 207 return HTCLIENT; 208 209 // When the window is restored we want a large click target above the tabs 210 // to drag the window, so redirect clicks in the tab's shadow to caption. 211 if (hit_test == HTCLIENT && 212 !(frame()->IsMaximized() || frame()->IsFullscreen())) { 213 // Convert point to client coordinates. 214 gfx::Point client_point(point); 215 View::ConvertPointToTarget(this, frame()->client_view(), &client_point); 216 // Report hits in shadow at top of tabstrip as caption. 217 gfx::Rect tabstrip_bounds(browser_view()->tabstrip()->bounds()); 218 if (client_point.y() < tabstrip_bounds.y() + kTabShadowHeight) 219 hit_test = HTCAPTION; 220 } 221 return hit_test; 222} 223 224void BrowserNonClientFrameViewAsh::GetWindowMask(const gfx::Size& size, 225 gfx::Path* window_mask) { 226 // Aura does not use window masks. 227} 228 229void BrowserNonClientFrameViewAsh::ResetWindowControls() { 230 // Hide the caption buttons in immersive fullscreen when the tab light bar 231 // is visible because it's confusing when the user hovers or clicks in the 232 // top-right of the screen and hits one. 233 bool button_visibility = !UseImmersiveLightbarHeaderStyle(); 234 caption_button_container_->SetVisible(button_visibility); 235 236 caption_button_container_->ResetWindowControls(); 237} 238 239void BrowserNonClientFrameViewAsh::UpdateWindowIcon() { 240 if (window_icon_) 241 window_icon_->SchedulePaint(); 242} 243 244void BrowserNonClientFrameViewAsh::UpdateWindowTitle() { 245 if (!frame()->IsFullscreen()) 246 header_painter_->SchedulePaintForTitle(); 247} 248 249/////////////////////////////////////////////////////////////////////////////// 250// views::View: 251 252void BrowserNonClientFrameViewAsh::OnPaint(gfx::Canvas* canvas) { 253 if (!ShouldPaint()) 254 return; 255 256 if (UseImmersiveLightbarHeaderStyle()) { 257 PaintImmersiveLightbarStyleHeader(canvas); 258 return; 259 } 260 261 caption_button_container_->SetPaintAsActive(ShouldPaintAsActive()); 262 263 ash::HeaderPainter::Mode header_mode = ShouldPaintAsActive() ? 264 ash::HeaderPainter::MODE_ACTIVE : ash::HeaderPainter::MODE_INACTIVE; 265 header_painter_->PaintHeader(canvas, header_mode); 266 if (browser_view()->IsToolbarVisible()) 267 PaintToolbarBackground(canvas); 268 else if (!UsePackagedAppHeaderStyle()) 269 PaintContentEdge(canvas); 270} 271 272void BrowserNonClientFrameViewAsh::Layout() { 273 // The header must be laid out before computing |painted_height| because the 274 // computation of |painted_height| for app and popup windows depends on the 275 // position of the window controls. 276 header_painter_->LayoutHeader(); 277 278 int painted_height = 0; 279 if (browser_view()->IsTabStripVisible()) { 280 painted_height = GetTopInset() + 281 browser_view()->tabstrip()->GetPreferredSize().height(); 282 } else if (browser_view()->IsToolbarVisible()) { 283 // Paint the header so that it overlaps with the top few pixels of the 284 // toolbar because the top few pixels of the toolbar are not opaque. 285 painted_height = GetTopInset() + kFrameShadowThickness * 2; 286 } else { 287 painted_height = GetTopInset(); 288 } 289 header_painter_->SetHeaderHeightForPainting(painted_height); 290 if (avatar_button()) 291 LayoutAvatar(); 292 BrowserNonClientFrameView::Layout(); 293} 294 295const char* BrowserNonClientFrameViewAsh::GetClassName() const { 296 return kViewClassName; 297} 298 299bool BrowserNonClientFrameViewAsh::HitTestRect(const gfx::Rect& rect) const { 300 if (!views::View::HitTestRect(rect)) { 301 // |rect| is outside BrowserNonClientFrameViewAsh's bounds. 302 return false; 303 } 304 305 TabStrip* tabstrip = browser_view()->tabstrip(); 306 if (tabstrip && browser_view()->IsTabStripVisible()) { 307 // Claim |rect| only if it is above the bottom of the tabstrip in a non-tab 308 // portion. 309 gfx::RectF rect_in_tabstrip_coords_f(rect); 310 View::ConvertRectToTarget(this, tabstrip, &rect_in_tabstrip_coords_f); 311 gfx::Rect rect_in_tabstrip_coords = gfx::ToEnclosingRect( 312 rect_in_tabstrip_coords_f); 313 314 if (rect_in_tabstrip_coords.y() > tabstrip->height()) 315 return false; 316 317 return !tabstrip->HitTestRect(rect_in_tabstrip_coords) || 318 tabstrip->IsRectInWindowCaption(rect_in_tabstrip_coords); 319 } 320 321 // Claim |rect| if it is above the top of the topmost view in the client area. 322 return rect.y() < GetTopInset(); 323} 324 325void BrowserNonClientFrameViewAsh::GetAccessibleState( 326 ui::AXViewState* state) { 327 state->role = ui::AX_ROLE_TITLE_BAR; 328} 329 330gfx::Size BrowserNonClientFrameViewAsh::GetMinimumSize() const { 331 gfx::Size min_client_view_size(frame()->client_view()->GetMinimumSize()); 332 int min_width = std::max(header_painter_->GetMinimumHeaderWidth(), 333 min_client_view_size.width()); 334 if (browser_view()->IsTabStripVisible()) { 335 // Ensure that the minimum width is enough to hold a minimum width tab strip 336 // at its usual insets. 337 int min_tabstrip_width = 338 browser_view()->tabstrip()->GetMinimumSize().width(); 339 min_width = std::max(min_width, 340 min_tabstrip_width + GetTabStripLeftInset() + GetTabStripRightInset()); 341 } 342 return gfx::Size(min_width, min_client_view_size.height()); 343} 344 345/////////////////////////////////////////////////////////////////////////////// 346// ash::ShellObserver: 347 348void BrowserNonClientFrameViewAsh::OnMaximizeModeStarted() { 349 caption_button_container_->UpdateSizeButtonVisibility(); 350 InvalidateLayout(); 351 frame()->client_view()->InvalidateLayout(); 352 frame()->GetRootView()->Layout(); 353} 354 355void BrowserNonClientFrameViewAsh::OnMaximizeModeEnded() { 356 caption_button_container_->UpdateSizeButtonVisibility(); 357 InvalidateLayout(); 358 frame()->client_view()->InvalidateLayout(); 359 frame()->GetRootView()->Layout(); 360} 361 362/////////////////////////////////////////////////////////////////////////////// 363// chrome::TabIconViewModel: 364 365bool BrowserNonClientFrameViewAsh::ShouldTabIconViewAnimate() const { 366 // This function is queried during the creation of the window as the 367 // TabIconView we host is initialized, so we need to NULL check the selected 368 // WebContents because in this condition there is not yet a selected tab. 369 content::WebContents* current_tab = browser_view()->GetActiveWebContents(); 370 return current_tab ? current_tab->IsLoading() : false; 371} 372 373gfx::ImageSkia BrowserNonClientFrameViewAsh::GetFaviconForTabIconView() { 374 views::WidgetDelegate* delegate = frame()->widget_delegate(); 375 if (!delegate) 376 return gfx::ImageSkia(); 377 return delegate->GetWindowIcon(); 378} 379 380/////////////////////////////////////////////////////////////////////////////// 381// views::View: 382 383void BrowserNonClientFrameViewAsh:: 384 ChildPreferredSizeChanged(views::View* child) { 385// FrameCaptionButtonContainerView animates the visibility changes in 386// UpdateSizeButtonVisibility(false). Due to this a new size is not available 387// until the completion of the animation. Layout in response to the preferred 388// size changes. 389 if (child != caption_button_container_) 390 return; 391 frame()->GetRootView()->Layout(); 392} 393 394/////////////////////////////////////////////////////////////////////////////// 395// BrowserNonClientFrameViewAsh, private: 396 397int BrowserNonClientFrameViewAsh::GetTabStripLeftInset() const { 398 return avatar_button() ? kAvatarSideSpacing + 399 browser_view()->GetOTRAvatarIcon().width() + kAvatarSideSpacing : 400 kTabstripLeftSpacing; 401} 402 403int BrowserNonClientFrameViewAsh::GetTabStripRightInset() const { 404 return caption_button_container_->GetPreferredSize().width() + 405 kTabstripRightSpacing; 406} 407 408bool BrowserNonClientFrameViewAsh::UseImmersiveLightbarHeaderStyle() const { 409 ImmersiveModeController* immersive_controller = 410 browser_view()->immersive_mode_controller(); 411 return immersive_controller->IsEnabled() && 412 !immersive_controller->IsRevealed() && 413 browser_view()->IsTabStripVisible(); 414} 415 416bool BrowserNonClientFrameViewAsh::UsePackagedAppHeaderStyle() const { 417 // Non streamlined hosted apps do not have a toolbar or tabstrip. Their header 418 // should look the same as the header for packaged apps. Streamlined hosted 419 // apps have a toolbar so should use the browser header style. 420 return browser_view()->browser()->is_app() && 421 !CommandLine::ForCurrentProcess()->HasSwitch( 422 switches::kEnableStreamlinedHostedApps); 423} 424 425void BrowserNonClientFrameViewAsh::LayoutAvatar() { 426 DCHECK(avatar_button()); 427#if !defined(OS_CHROMEOS) 428 // ChromeOS shows avatar on V1 app. 429 DCHECK(browser_view()->IsTabStripVisible()); 430#endif 431 gfx::ImageSkia incognito_icon = browser_view()->GetOTRAvatarIcon(); 432 433 int avatar_bottom = GetTopInset() + 434 browser_view()->GetTabStripHeight() - kAvatarBottomSpacing; 435 int avatar_restored_y = avatar_bottom - incognito_icon.height(); 436 int avatar_y = 437 (browser_view()->IsTabStripVisible() && 438 (frame()->IsMaximized() || frame()->IsFullscreen())) ? 439 GetTopInset() + kContentShadowHeight : avatar_restored_y; 440 441 // Hide the incognito icon in immersive fullscreen when the tab light bar is 442 // visible because the header is too short for the icognito icon to be 443 // recognizable. 444 bool avatar_visible = !UseImmersiveLightbarHeaderStyle(); 445 int avatar_height = avatar_visible ? avatar_bottom - avatar_y : 0; 446 447 gfx::Rect avatar_bounds(kAvatarSideSpacing, 448 avatar_y, 449 incognito_icon.width(), 450 avatar_height); 451 avatar_button()->SetBoundsRect(avatar_bounds); 452 avatar_button()->SetVisible(avatar_visible); 453} 454 455bool BrowserNonClientFrameViewAsh::ShouldPaint() const { 456 if (!frame()->IsFullscreen()) 457 return true; 458 459 // We need to paint when in immersive fullscreen and either: 460 // - The top-of-window views are revealed. 461 // - The lightbar style tabstrip is visible. 462 ImmersiveModeController* immersive_mode_controller = 463 browser_view()->immersive_mode_controller(); 464 return immersive_mode_controller->IsEnabled() && 465 (immersive_mode_controller->IsRevealed() || 466 UseImmersiveLightbarHeaderStyle()); 467} 468 469void BrowserNonClientFrameViewAsh::PaintImmersiveLightbarStyleHeader( 470 gfx::Canvas* canvas) { 471 // The light bar header is not themed because theming it does not look good. 472 canvas->FillRect( 473 gfx::Rect(width(), header_painter_->GetHeaderHeightForPainting()), 474 SK_ColorBLACK); 475} 476 477void BrowserNonClientFrameViewAsh::PaintToolbarBackground(gfx::Canvas* canvas) { 478 gfx::Rect toolbar_bounds(browser_view()->GetToolbarBounds()); 479 if (toolbar_bounds.IsEmpty()) 480 return; 481 gfx::Point toolbar_origin(toolbar_bounds.origin()); 482 View::ConvertPointToTarget(browser_view(), this, &toolbar_origin); 483 toolbar_bounds.set_origin(toolbar_origin); 484 485 int x = toolbar_bounds.x(); 486 int w = toolbar_bounds.width(); 487 int y = toolbar_bounds.y(); 488 int h = toolbar_bounds.height(); 489 490 // Gross hack: We split the toolbar images into two pieces, since sometimes 491 // (popup mode) the toolbar isn't tall enough to show the whole image. The 492 // split happens between the top shadow section and the bottom gradient 493 // section so that we never break the gradient. 494 // NOTE(pkotwicz): If the computation for |bottom_y| is changed, Layout() must 495 // be changed as well. 496 int split_point = kFrameShadowThickness * 2; 497 int bottom_y = y + split_point; 498 ui::ThemeProvider* tp = GetThemeProvider(); 499 int bottom_edge_height = h - split_point; 500 501 canvas->FillRect(gfx::Rect(x, bottom_y, w, bottom_edge_height), 502 tp->GetColor(ThemeProperties::COLOR_TOOLBAR)); 503 504 // Paint the main toolbar image. Since this image is also used to draw the 505 // tab background, we must use the tab strip offset to compute the image 506 // source y position. If you have to debug this code use an image editor 507 // to paint a diagonal line through the toolbar image and ensure it lines up 508 // across the tab and toolbar. 509 gfx::ImageSkia* theme_toolbar = tp->GetImageSkiaNamed(IDR_THEME_TOOLBAR); 510 canvas->TileImageInt( 511 *theme_toolbar, 512 x + GetThemeBackgroundXInset(), 513 bottom_y - GetTopInset(), 514 x, bottom_y, 515 w, theme_toolbar->height()); 516 517 // The content area line has a shadow that extends a couple of pixels above 518 // the toolbar bounds. 519 const int kContentShadowHeight = 2; 520 gfx::ImageSkia* toolbar_top = tp->GetImageSkiaNamed(IDR_TOOLBAR_SHADE_TOP); 521 canvas->TileImageInt(*toolbar_top, 522 0, 0, 523 x, y - kContentShadowHeight, 524 w, split_point + kContentShadowHeight + 1); 525 526 // Draw the "lightening" shade line around the edges of the toolbar. 527 gfx::ImageSkia* toolbar_left = tp->GetImageSkiaNamed(IDR_TOOLBAR_SHADE_LEFT); 528 canvas->TileImageInt(*toolbar_left, 529 0, 0, 530 x + kClientEdgeThickness, 531 y + kClientEdgeThickness + kContentShadowHeight, 532 toolbar_left->width(), theme_toolbar->height()); 533 gfx::ImageSkia* toolbar_right = 534 tp->GetImageSkiaNamed(IDR_TOOLBAR_SHADE_RIGHT); 535 canvas->TileImageInt(*toolbar_right, 536 0, 0, 537 w - toolbar_right->width() - 2 * kClientEdgeThickness, 538 y + kClientEdgeThickness + kContentShadowHeight, 539 toolbar_right->width(), theme_toolbar->height()); 540 541 // Draw the content/toolbar separator. 542 canvas->FillRect( 543 gfx::Rect(x + kClientEdgeThickness, 544 toolbar_bounds.bottom() - kClientEdgeThickness, 545 w - (2 * kClientEdgeThickness), 546 kClientEdgeThickness), 547 ThemeProperties::GetDefaultColor( 548 ThemeProperties::COLOR_TOOLBAR_SEPARATOR)); 549} 550 551void BrowserNonClientFrameViewAsh::PaintContentEdge(gfx::Canvas* canvas) { 552 DCHECK(!UsePackagedAppHeaderStyle()); 553 canvas->FillRect(gfx::Rect(0, caption_button_container_->bounds().bottom(), 554 width(), kClientEdgeThickness), 555 ThemeProperties::GetDefaultColor( 556 ThemeProperties::COLOR_TOOLBAR_SEPARATOR)); 557} 558