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 "ui/views/controls/button/label_button.h"
6 
7 #include <stddef.h>
8 
9 #include <algorithm>
10 #include <utility>
11 
12 #include "base/lazy_instance.h"
13 #include "base/logging.h"
14 #include "build/build_config.h"
15 #include "ui/accessibility/ax_enums.mojom.h"
16 #include "ui/accessibility/ax_node_data.h"
17 #include "ui/gfx/animation/throb_animation.h"
18 #include "ui/gfx/canvas.h"
19 #include "ui/gfx/color_utils.h"
20 #include "ui/gfx/font_list.h"
21 #include "ui/gfx/geometry/vector2d.h"
22 #include "ui/native_theme/native_theme.h"
23 #include "ui/views/animation/ink_drop.h"
24 #include "ui/views/background.h"
25 #include "ui/views/controls/button/label_button_border.h"
26 #include "ui/views/metadata/metadata_impl_macros.h"
27 #include "ui/views/painter.h"
28 #include "ui/views/style/platform_style.h"
29 #include "ui/views/view_class_properties.h"
30 #include "ui/views/window/dialog_delegate.h"
31 
32 namespace views {
33 
LabelButton(ButtonListener * listener,const base::string16 & text,int button_context)34 LabelButton::LabelButton(ButtonListener* listener,
35                          const base::string16& text,
36                          int button_context)
37     : Button(listener),
38       cached_normal_font_list_(
39           style::GetFont(button_context, style::STYLE_PRIMARY)),
40       cached_default_button_font_list_(
41           style::GetFont(button_context, style::STYLE_DIALOG_BUTTON_DEFAULT)) {
42   ink_drop_container_ = AddChildView(std::make_unique<InkDropContainerView>());
43   ink_drop_container_->SetVisible(false);
44 
45   image_ = AddChildView(std::make_unique<ImageView>());
46   image_->set_can_process_events_within_subtree(false);
47 
48   label_ =
49       AddChildView(std::make_unique<LabelButtonLabel>(text, button_context));
50   label_->SetAutoColorReadabilityEnabled(false);
51   label_->SetHorizontalAlignment(gfx::ALIGN_TO_HEAD);
52 
53   SetAnimationDuration(base::TimeDelta::FromMilliseconds(170));
54   SetTextInternal(text);
55 }
56 
57 LabelButton::~LabelButton() = default;
58 
GetImage(ButtonState for_state) const59 gfx::ImageSkia LabelButton::GetImage(ButtonState for_state) const {
60   if (for_state != STATE_NORMAL && button_state_images_[for_state].isNull())
61     return button_state_images_[STATE_NORMAL];
62   return button_state_images_[for_state];
63 }
64 
SetImage(ButtonState for_state,const gfx::ImageSkia & image)65 void LabelButton::SetImage(ButtonState for_state, const gfx::ImageSkia& image) {
66   button_state_images_[for_state] = image;
67   UpdateImage();
68 }
69 
GetText() const70 const base::string16& LabelButton::GetText() const {
71   return label_->GetText();
72 }
73 
SetText(const base::string16 & text)74 void LabelButton::SetText(const base::string16& text) {
75   SetTextInternal(text);
76 }
77 
ShrinkDownThenClearText()78 void LabelButton::ShrinkDownThenClearText() {
79   if (GetText().empty())
80     return;
81   // First, we recalculate preferred size for the new mode (without the label).
82   shrinking_down_label_ = true;
83   PreferredSizeChanged();
84   // Second, we clear the label right away if the button is already small.
85   ClearTextIfShrunkDown();
86 }
87 
SetTextColor(ButtonState for_state,SkColor color)88 void LabelButton::SetTextColor(ButtonState for_state, SkColor color) {
89   button_state_colors_[for_state] = color;
90   if (for_state == STATE_DISABLED)
91     label_->SetDisabledColor(color);
92   else if (for_state == state())
93     label_->SetEnabledColor(color);
94   explicitly_set_colors_[for_state] = true;
95 }
96 
SetEnabledTextColors(base::Optional<SkColor> color)97 void LabelButton::SetEnabledTextColors(base::Optional<SkColor> color) {
98   ButtonState states[] = {STATE_NORMAL, STATE_HOVERED, STATE_PRESSED};
99   if (color.has_value()) {
100     for (auto state : states)
101       SetTextColor(state, color.value());
102     return;
103   }
104   for (auto state : states)
105     explicitly_set_colors_[state] = false;
106   ResetColorsFromNativeTheme();
107 }
108 
SetTextShadows(const gfx::ShadowValues & shadows)109 void LabelButton::SetTextShadows(const gfx::ShadowValues& shadows) {
110   label_->SetShadows(shadows);
111 }
112 
SetTextSubpixelRenderingEnabled(bool enabled)113 void LabelButton::SetTextSubpixelRenderingEnabled(bool enabled) {
114   label_->SetSubpixelRenderingEnabled(enabled);
115 }
116 
SetElideBehavior(gfx::ElideBehavior elide_behavior)117 void LabelButton::SetElideBehavior(gfx::ElideBehavior elide_behavior) {
118   label_->SetElideBehavior(elide_behavior);
119 }
120 
SetHorizontalAlignment(gfx::HorizontalAlignment alignment)121 void LabelButton::SetHorizontalAlignment(gfx::HorizontalAlignment alignment) {
122   DCHECK_NE(gfx::ALIGN_TO_HEAD, alignment);
123   if (GetHorizontalAlignment() == alignment)
124     return;
125   horizontal_alignment_ = alignment;
126   OnPropertyChanged(&min_size_, kPropertyEffectsLayout);
127 }
128 
GetHorizontalAlignment() const129 gfx::HorizontalAlignment LabelButton::GetHorizontalAlignment() const {
130   return horizontal_alignment_;
131 }
132 
GetMinSize() const133 gfx::Size LabelButton::GetMinSize() const {
134   return min_size_;
135 }
136 
SetMinSize(const gfx::Size & min_size)137 void LabelButton::SetMinSize(const gfx::Size& min_size) {
138   if (GetMinSize() == min_size)
139     return;
140   min_size_ = min_size;
141   ResetCachedPreferredSize();
142   OnPropertyChanged(&min_size_, kPropertyEffectsNone);
143 }
144 
GetMaxSize() const145 gfx::Size LabelButton::GetMaxSize() const {
146   return max_size_;
147 }
148 
SetMaxSize(const gfx::Size & max_size)149 void LabelButton::SetMaxSize(const gfx::Size& max_size) {
150   if (GetMaxSize() == max_size)
151     return;
152   max_size_ = max_size;
153   ResetCachedPreferredSize();
154   OnPropertyChanged(&max_size_, kPropertyEffectsNone);
155 }
156 
GetIsDefault() const157 bool LabelButton::GetIsDefault() const {
158   return is_default_;
159 }
160 
SetIsDefault(bool is_default)161 void LabelButton::SetIsDefault(bool is_default) {
162   // TODO(estade): move this to MdTextButton once |style_| is removed.
163   if (GetIsDefault() == is_default)
164     return;
165   is_default_ = is_default;
166   ui::Accelerator accel(ui::VKEY_RETURN, ui::EF_NONE);
167   if (is_default)
168     AddAccelerator(accel);
169   else
170     RemoveAccelerator(accel);
171   OnPropertyChanged(&is_default_, UpdateStyleToIndicateDefaultStatus());
172 }
173 
GetImageLabelSpacing() const174 int LabelButton::GetImageLabelSpacing() const {
175   return image_label_spacing_;
176 }
177 
SetImageLabelSpacing(int spacing)178 void LabelButton::SetImageLabelSpacing(int spacing) {
179   if (GetImageLabelSpacing() == spacing)
180     return;
181   image_label_spacing_ = spacing;
182   ResetCachedPreferredSize();
183   OnPropertyChanged(&image_label_spacing_, kPropertyEffectsLayout);
184 }
185 
GetImageCentered() const186 bool LabelButton::GetImageCentered() const {
187   return image_centered_;
188 }
189 
SetImageCentered(bool image_centered)190 void LabelButton::SetImageCentered(bool image_centered) {
191   if (GetImageCentered() == image_centered)
192     return;
193   image_centered_ = image_centered;
194   OnPropertyChanged(&image_centered_, kPropertyEffectsLayout);
195 }
196 
CreateDefaultBorder() const197 std::unique_ptr<LabelButtonBorder> LabelButton::CreateDefaultBorder() const {
198   auto border = std::make_unique<LabelButtonBorder>();
199   border->set_insets(views::LabelButtonAssetBorder::GetDefaultInsets());
200   return border;
201 }
202 
SetBorder(std::unique_ptr<Border> border)203 void LabelButton::SetBorder(std::unique_ptr<Border> border) {
204   border_is_themed_border_ = false;
205   View::SetBorder(std::move(border));
206   ResetCachedPreferredSize();
207 }
208 
OnBoundsChanged(const gfx::Rect & previous_bounds)209 void LabelButton::OnBoundsChanged(const gfx::Rect& previous_bounds) {
210   ClearTextIfShrunkDown();
211   Button::OnBoundsChanged(previous_bounds);
212 }
213 
CalculatePreferredSize() const214 gfx::Size LabelButton::CalculatePreferredSize() const {
215   // Cache the computed size, as recomputing it is an expensive operation.
216   if (!cached_preferred_size_) {
217     gfx::Size size = GetUnclampedSizeWithoutLabel();
218 
219     // Disregard label in the preferred size if the button is shrinking down to
220     // show no label soon.
221     if (!shrinking_down_label_) {
222       const gfx::Size preferred_label_size = label_->GetPreferredSize();
223       size.Enlarge(preferred_label_size.width(), 0);
224 
225       // Increase the height of the label (with insets) if larger.
226       size.set_height(std::max(
227           preferred_label_size.height() + GetInsets().height(), size.height()));
228     }
229 
230     size.SetToMax(GetMinSize());
231 
232     // Clamp size to max size (if valid).
233     const gfx::Size max_size = GetMaxSize();
234     if (max_size.width() > 0)
235       size.set_width(std::min(max_size.width(), size.width()));
236     if (max_size.height() > 0)
237       size.set_height(std::min(max_size.height(), size.height()));
238 
239     cached_preferred_size_ = size;
240   }
241 
242   return cached_preferred_size_.value();
243 }
244 
GetMinimumSize() const245 gfx::Size LabelButton::GetMinimumSize() const {
246   if (label_->GetElideBehavior() == gfx::ElideBehavior::NO_ELIDE)
247     return GetPreferredSize();
248 
249   gfx::Size size = image_->GetPreferredSize();
250   const gfx::Insets insets(GetInsets());
251   size.Enlarge(insets.width(), insets.height());
252 
253   size.SetToMax(GetMinSize());
254   const gfx::Size max_size = GetMaxSize();
255   if (max_size.width() > 0)
256     size.set_width(std::min(max_size.width(), size.width()));
257   if (max_size.height() > 0)
258     size.set_height(std::min(max_size.height(), size.height()));
259 
260   return size;
261 }
262 
GetHeightForWidth(int width) const263 int LabelButton::GetHeightForWidth(int width) const {
264   const gfx::Size size_without_label = GetUnclampedSizeWithoutLabel();
265   // Get label height for the remaining width.
266   const int label_height_with_insets =
267       label_->GetHeightForWidth(width - size_without_label.width()) +
268       GetInsets().height();
269 
270   // Height is the larger of size without label and label height with insets.
271   int height = std::max(size_without_label.height(), label_height_with_insets);
272 
273   height = std::max(height, GetMinSize().height());
274 
275   // Clamp height to the maximum height (if valid).
276   const gfx::Size max_size = GetMaxSize();
277   if (max_size.height() > 0)
278     return std::min(max_size.height(), height);
279 
280   return height;
281 }
282 
Layout()283 void LabelButton::Layout() {
284   gfx::Rect child_area = GetLocalBounds();
285 
286   ink_drop_container_->SetBoundsRect(child_area);
287   // The space that the label can use. Its actual bounds may be smaller if the
288   // label is short.
289   gfx::Rect label_area = child_area;
290 
291   gfx::Insets insets = GetInsets();
292   child_area.Inset(insets);
293   // Labels can paint over the vertical component of the border insets.
294   label_area.Inset(insets.left(), 0, insets.right(), 0);
295 
296   gfx::Size image_size = image_->GetPreferredSize();
297   image_size.SetToMin(child_area.size());
298 
299   const auto horizontal_alignment = GetHorizontalAlignment();
300   if (!image_size.IsEmpty()) {
301     int image_space = image_size.width() + GetImageLabelSpacing();
302     if (horizontal_alignment == gfx::ALIGN_RIGHT)
303       label_area.Inset(0, 0, image_space, 0);
304     else
305       label_area.Inset(image_space, 0, 0, 0);
306   }
307 
308   gfx::Size label_size(
309       std::min(label_area.width(), label_->GetPreferredSize().width()),
310       label_area.height());
311 
312   gfx::Point image_origin = child_area.origin();
313   if (label_->GetMultiLine() && !image_centered_) {
314     image_origin.Offset(
315         0, std::max(
316                0, (label_->font_list().GetHeight() - image_size.height()) / 2));
317   } else {
318     image_origin.Offset(0, (child_area.height() - image_size.height()) / 2);
319   }
320   if (horizontal_alignment == gfx::ALIGN_CENTER) {
321     const int spacing = (image_size.width() > 0 && label_size.width() > 0)
322                             ? GetImageLabelSpacing()
323                             : 0;
324     const int total_width = image_size.width() + label_size.width() + spacing;
325     image_origin.Offset((child_area.width() - total_width) / 2, 0);
326   } else if (horizontal_alignment == gfx::ALIGN_RIGHT) {
327     image_origin.Offset(child_area.width() - image_size.width(), 0);
328   }
329   image_->SetBoundsRect(gfx::Rect(image_origin, image_size));
330 
331   gfx::Rect label_bounds = label_area;
332   if (label_area.width() == label_size.width()) {
333     // Label takes up the whole area.
334   } else if (horizontal_alignment == gfx::ALIGN_CENTER) {
335     label_bounds.ClampToCenteredSize(label_size);
336   } else {
337     label_bounds.set_size(label_size);
338     if (horizontal_alignment == gfx::ALIGN_RIGHT)
339       label_bounds.Offset(label_area.width() - label_size.width(), 0);
340   }
341 
342   label_->SetBoundsRect(label_bounds);
343   Button::Layout();
344 }
345 
EnableCanvasFlippingForRTLUI(bool flip)346 void LabelButton::EnableCanvasFlippingForRTLUI(bool flip) {
347   Button::EnableCanvasFlippingForRTLUI(flip);
348   image_->EnableCanvasFlippingForRTLUI(flip);
349 }
350 
GetAccessibleNodeData(ui::AXNodeData * node_data)351 void LabelButton::GetAccessibleNodeData(ui::AXNodeData* node_data) {
352   if (GetIsDefault())
353     node_data->AddState(ax::mojom::State::kDefault);
354   Button::GetAccessibleNodeData(node_data);
355 }
356 
GetThemePart() const357 ui::NativeTheme::Part LabelButton::GetThemePart() const {
358   return ui::NativeTheme::kPushButton;
359 }
360 
GetThemePaintRect() const361 gfx::Rect LabelButton::GetThemePaintRect() const {
362   return GetLocalBounds();
363 }
364 
GetThemeState(ui::NativeTheme::ExtraParams * params) const365 ui::NativeTheme::State LabelButton::GetThemeState(
366     ui::NativeTheme::ExtraParams* params) const {
367   GetExtraParams(params);
368   switch (state()) {
369     case STATE_NORMAL:
370       return ui::NativeTheme::kNormal;
371     case STATE_HOVERED:
372       return ui::NativeTheme::kHovered;
373     case STATE_PRESSED:
374       return ui::NativeTheme::kPressed;
375     case STATE_DISABLED:
376       return ui::NativeTheme::kDisabled;
377     case STATE_COUNT:
378       NOTREACHED() << "Unknown state: " << state();
379   }
380   return ui::NativeTheme::kNormal;
381 }
382 
GetThemeAnimation() const383 const gfx::Animation* LabelButton::GetThemeAnimation() const {
384   return &hover_animation();
385 }
386 
GetBackgroundThemeState(ui::NativeTheme::ExtraParams * params) const387 ui::NativeTheme::State LabelButton::GetBackgroundThemeState(
388     ui::NativeTheme::ExtraParams* params) const {
389   GetExtraParams(params);
390   return ui::NativeTheme::kNormal;
391 }
392 
GetForegroundThemeState(ui::NativeTheme::ExtraParams * params) const393 ui::NativeTheme::State LabelButton::GetForegroundThemeState(
394     ui::NativeTheme::ExtraParams* params) const {
395   GetExtraParams(params);
396   return ui::NativeTheme::kHovered;
397 }
398 
UpdateImage()399 void LabelButton::UpdateImage() {
400   image_->SetImage(GetImage(GetVisualState()));
401   ResetCachedPreferredSize();
402 }
403 
UpdateThemedBorder()404 void LabelButton::UpdateThemedBorder() {
405   // Don't override borders set by others.
406   if (!border_is_themed_border_)
407     return;
408 
409   SetBorder(PlatformStyle::CreateThemedLabelButtonBorder(this));
410   border_is_themed_border_ = true;
411 }
412 
AddLayerBeneathView(ui::Layer * new_layer)413 void LabelButton::AddLayerBeneathView(ui::Layer* new_layer) {
414   image()->SetPaintToLayer();
415   image()->layer()->SetFillsBoundsOpaquely(false);
416   ink_drop_container()->SetVisible(true);
417   ink_drop_container()->AddLayerBeneathView(new_layer);
418 }
419 
RemoveLayerBeneathView(ui::Layer * old_layer)420 void LabelButton::RemoveLayerBeneathView(ui::Layer* old_layer) {
421   ink_drop_container()->RemoveLayerBeneathView(old_layer);
422   ink_drop_container()->SetVisible(false);
423   image()->DestroyLayer();
424 }
425 
GetExtraParams(ui::NativeTheme::ExtraParams * params) const426 void LabelButton::GetExtraParams(ui::NativeTheme::ExtraParams* params) const {
427   params->button.checked = false;
428   params->button.indeterminate = false;
429   params->button.is_default = GetIsDefault();
430   params->button.is_focused = HasFocus() && IsAccessibilityFocusable();
431   params->button.has_border = false;
432   params->button.classic_state = 0;
433   params->button.background_color = label_->GetBackgroundColor();
434 }
435 
UpdateStyleToIndicateDefaultStatus()436 PropertyEffects LabelButton::UpdateStyleToIndicateDefaultStatus() {
437   // Check that a subclass hasn't replaced the Label font. These buttons may
438   // never be given default status.
439   DCHECK_EQ(cached_normal_font_list_.GetFontSize(),
440             label()->font_list().GetFontSize());
441   // TODO(tapted): This should use style::GetFont(), but this part can just be
442   // deleted when default buttons no longer go bold. Colors will need updating
443   // still.
444   label_->SetFontList(GetIsDefault() ? cached_default_button_font_list_
445                                      : cached_normal_font_list_);
446   ResetLabelEnabledColor();
447   return kPropertyEffectsLayout;
448 }
449 
ChildPreferredSizeChanged(View * child)450 void LabelButton::ChildPreferredSizeChanged(View* child) {
451   PreferredSizeChanged();
452 }
453 
PreferredSizeChanged()454 void LabelButton::PreferredSizeChanged() {
455   ResetCachedPreferredSize();
456   Button::PreferredSizeChanged();
457 }
458 
OnFocus()459 void LabelButton::OnFocus() {
460   Button::OnFocus();
461   // Typically the border renders differently when focused.
462   SchedulePaint();
463 }
464 
OnBlur()465 void LabelButton::OnBlur() {
466   Button::OnBlur();
467   // Typically the border renders differently when focused.
468   SchedulePaint();
469 }
470 
OnThemeChanged()471 void LabelButton::OnThemeChanged() {
472   Button::OnThemeChanged();
473   ResetColorsFromNativeTheme();
474   UpdateThemedBorder();
475   ResetLabelEnabledColor();
476   // Invalidate the layout to pickup the new insets from the border.
477   InvalidateLayout();
478   // The entire button has to be repainted here, since the native theme can
479   // define the tint for the entire background/border/focus ring.
480   SchedulePaint();
481 }
482 
StateChanged(ButtonState old_state)483 void LabelButton::StateChanged(ButtonState old_state) {
484   const gfx::Size previous_image_size(image_->GetPreferredSize());
485   UpdateImage();
486   ResetLabelEnabledColor();
487   label_->SetEnabled(state() != STATE_DISABLED);
488   if (image_->GetPreferredSize() != previous_image_size)
489     InvalidateLayout();
490   Button::StateChanged(old_state);
491 }
492 
SetTextInternal(const base::string16 & text)493 void LabelButton::SetTextInternal(const base::string16& text) {
494   SetAccessibleName(text);
495   label_->SetText(text);
496 
497   // Setting text cancels ShrinkDownThenClearText().
498   if (shrinking_down_label_) {
499     shrinking_down_label_ = false;
500     PreferredSizeChanged();
501   }
502 
503   // TODO(pkasting): Remove this and forward callback subscriptions to the
504   // underlying label property when Label is converted to properties.
505   OnPropertyChanged(label_, kPropertyEffectsNone);
506 }
507 
ClearTextIfShrunkDown()508 void LabelButton::ClearTextIfShrunkDown() {
509   if (!cached_preferred_size_)
510     CalculatePreferredSize();
511   if (shrinking_down_label_ && width() <= cached_preferred_size_->width() &&
512       height() <= cached_preferred_size_->height()) {
513     // Once the button shrinks down to its preferred size (that disregards the
514     // current text), we finish the operation by clearing the text.
515     shrinking_down_label_ = false;
516     SetText(base::string16());
517   }
518 }
519 
ResetCachedPreferredSize()520 void LabelButton::ResetCachedPreferredSize() {
521   cached_preferred_size_ = base::nullopt;
522 }
523 
GetUnclampedSizeWithoutLabel() const524 gfx::Size LabelButton::GetUnclampedSizeWithoutLabel() const {
525   const gfx::Size image_size = image_->GetPreferredSize();
526   gfx::Size size = image_size;
527   const gfx::Insets insets(GetInsets());
528   size.Enlarge(insets.width(), insets.height());
529 
530   // Accommodate for spacing between image and text if both are present.
531   if (image_size.width() > 0 && !GetText().empty() && !shrinking_down_label_)
532     size.Enlarge(GetImageLabelSpacing(), 0);
533 
534   // Make the size at least as large as the minimum size needed by the border.
535   if (border())
536     size.SetToMax(border()->GetMinimumSize());
537 
538   return size;
539 }
540 
ResetColorsFromNativeTheme()541 void LabelButton::ResetColorsFromNativeTheme() {
542   const ui::NativeTheme* theme = GetNativeTheme();
543   // Since this is a LabelButton, use the label colors.
544   SkColor colors[STATE_COUNT] = {
545       theme->GetSystemColor(ui::NativeTheme::kColorId_LabelEnabledColor),
546       theme->GetSystemColor(ui::NativeTheme::kColorId_LabelEnabledColor),
547       theme->GetSystemColor(ui::NativeTheme::kColorId_LabelEnabledColor),
548       theme->GetSystemColor(ui::NativeTheme::kColorId_LabelDisabledColor),
549   };
550 
551   label_->SetBackground(nullptr);
552   label_->SetAutoColorReadabilityEnabled(false);
553 
554   for (size_t state = STATE_NORMAL; state < STATE_COUNT; ++state) {
555     if (!explicitly_set_colors_[state]) {
556       SetTextColor(static_cast<ButtonState>(state), colors[state]);
557       explicitly_set_colors_[state] = false;
558     }
559   }
560 }
561 
ResetLabelEnabledColor()562 void LabelButton::ResetLabelEnabledColor() {
563   const SkColor color = button_state_colors_[state()];
564   if (state() != STATE_DISABLED && label_->GetEnabledColor() != color)
565     label_->SetEnabledColor(color);
566 }
567 
568 BEGIN_METADATA(LabelButton)
569 METADATA_PARENT_CLASS(Button)
570 ADD_PROPERTY_METADATA(LabelButton, base::string16, Text)
571 ADD_PROPERTY_METADATA(LabelButton,
572                       gfx::HorizontalAlignment,
573                       HorizontalAlignment)
574 ADD_PROPERTY_METADATA(LabelButton, gfx::Size, MinSize)
575 ADD_PROPERTY_METADATA(LabelButton, gfx::Size, MaxSize)
576 ADD_PROPERTY_METADATA(LabelButton, bool, IsDefault)
577 ADD_PROPERTY_METADATA(LabelButton, int, ImageLabelSpacing)
578 ADD_PROPERTY_METADATA(LabelButton, bool, ImageCentered)
579 END_METADATA()
580 
581 }  // namespace views
582