summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorjonross <jonross@chromium.org>2014-10-08 11:18:20 -0700
committerCommit bot <commit-bot@chromium.org>2014-10-08 18:19:03 +0000
commit4ccdc6d40988948297afc0439037d93ef70f3773 (patch)
treebeded26291c0debfd36df6eac6c73eb64ee3a345
parenta4745ddca4103480cad0d0b4318d9bf301483440 (diff)
downloadchromium_src-4ccdc6d40988948297afc0439037d93ef70f3773.zip
chromium_src-4ccdc6d40988948297afc0439037d93ef70f3773.tar.gz
chromium_src-4ccdc6d40988948297afc0439037d93ef70f3773.tar.bz2
Status Panel Touch Feedback
Add touch feedback to HoverHighlightView and TrayPopupItemContainer. These are responsible for rendering the backgrounds of items that appear in the expanded SystemTray. This covers both the default and the detailed views of system items. This also covers the highlight textof the back button to transition from detailed views to defaults views. TrayPopupItemContainer was moved to its own file to allow for tests to be added in SystemTrayTest TEST=SystemTrayTest.TrayPopupItemContainerTouchFeedback, SystemTrayTest.TrayPopupItemContainerTouchFeedbackCancellation, TrayDetailsViewTest.HoverHighlightViewTouchFeedback, TrayDetailsViewTest.HoverHighlightViewTouchFeedbackCancellation BUG=398398 Review URL: https://codereview.chromium.org/556383002 Cr-Commit-Position: refs/heads/master@{#298748}
-rw-r--r--ash/ash.gyp2
-rw-r--r--ash/system/tray/hover_highlight_view.cc36
-rw-r--r--ash/system/tray/hover_highlight_view.h4
-rw-r--r--ash/system/tray/system_tray_bubble.cc68
-rw-r--r--ash/system/tray/system_tray_unittest.cc73
-rw-r--r--ash/system/tray/tray_details_view_unittest.cc85
-rw-r--r--ash/system/tray/tray_popup_item_container.cc88
-rw-r--r--ash/system/tray/tray_popup_item_container.h51
8 files changed, 324 insertions, 83 deletions
diff --git a/ash/ash.gyp b/ash/ash.gyp
index 4c232b6..6aa2dd2 100644
--- a/ash/ash.gyp
+++ b/ash/ash.gyp
@@ -427,6 +427,8 @@
'system/tray/tray_notification_view.h',
'system/tray/tray_popup_header_button.cc',
'system/tray/tray_popup_header_button.h',
+ 'system/tray/tray_popup_item_container.cc',
+ 'system/tray/tray_popup_item_container.h',
'system/tray/tray_popup_label_button.cc',
'system/tray/tray_popup_label_button.h',
'system/tray/tray_popup_label_button_border.cc',
diff --git a/ash/system/tray/hover_highlight_view.cc b/ash/system/tray/hover_highlight_view.cc
index be2efc2..d81f5a6 100644
--- a/ash/system/tray/hover_highlight_view.cc
+++ b/ash/system/tray/hover_highlight_view.cc
@@ -9,6 +9,7 @@
#include "ash/system/tray/view_click_listener.h"
#include "ui/accessibility/ax_view_state.h"
#include "ui/base/resource/resource_bundle.h"
+#include "ui/base/ui_base_switches_util.h"
#include "ui/gfx/canvas.h"
#include "ui/gfx/font_list.h"
#include "ui/resources/grit/ui_resources.h"
@@ -130,6 +131,19 @@ void HoverHighlightView::SetExpandable(bool expandable) {
}
}
+void HoverHighlightView::SetHoverHighlight(bool hover) {
+ if (hover_ == hover)
+ return;
+ hover_ = hover;
+ if (!text_label_)
+ return;
+ if (hover_ && text_highlight_color_)
+ text_label_->SetEnabledColor(text_highlight_color_);
+ if (!hover_ && text_default_color_)
+ text_label_->SetEnabledColor(text_default_color_);
+ SchedulePaint();
+}
+
bool HoverHighlightView::PerformAction(const ui::Event& event) {
if (!listener_)
return false;
@@ -159,17 +173,23 @@ int HoverHighlightView::GetHeightForWidth(int width) const {
}
void HoverHighlightView::OnMouseEntered(const ui::MouseEvent& event) {
- hover_ = true;
- if (text_highlight_color_ && text_label_)
- text_label_->SetEnabledColor(text_highlight_color_);
- SchedulePaint();
+ SetHoverHighlight(true);
}
void HoverHighlightView::OnMouseExited(const ui::MouseEvent& event) {
- hover_ = false;
- if (text_default_color_ && text_label_)
- text_label_->SetEnabledColor(text_default_color_);
- SchedulePaint();
+ SetHoverHighlight(false);
+}
+
+void HoverHighlightView::OnGestureEvent(ui::GestureEvent* event) {
+ if (switches::IsTouchFeedbackEnabled()) {
+ if (event->type() == ui::ET_GESTURE_TAP_DOWN) {
+ SetHoverHighlight(true);
+ } else if (event->type() == ui::ET_GESTURE_TAP_CANCEL ||
+ event->type() == ui::ET_GESTURE_TAP) {
+ SetHoverHighlight(false);
+ }
+ }
+ ActionableView::OnGestureEvent(event);
}
void HoverHighlightView::OnEnabledChanged() {
diff --git a/ash/system/tray/hover_highlight_view.h b/ash/system/tray/hover_highlight_view.h
index 6df822f..ffd83a2 100644
--- a/ash/system/tray/hover_highlight_view.h
+++ b/ash/system/tray/hover_highlight_view.h
@@ -64,6 +64,9 @@ class HoverHighlightView : public ActionableView {
virtual void GetAccessibleState(ui::AXViewState* state) override;
private:
+ // Sets the highlighted color on a text label if |hover| is set.
+ void SetHoverHighlight(bool hover);
+
// Overridden from ActionableView:
virtual bool PerformAction(const ui::Event& event) override;
@@ -72,6 +75,7 @@ class HoverHighlightView : public ActionableView {
virtual int GetHeightForWidth(int width) const override;
virtual void OnMouseEntered(const ui::MouseEvent& event) override;
virtual void OnMouseExited(const ui::MouseEvent& event) override;
+ virtual void OnGestureEvent(ui::GestureEvent* event) override;
virtual void OnEnabledChanged() override;
virtual void OnPaintBackground(gfx::Canvas* canvas) override;
virtual void OnFocus() override;
diff --git a/ash/system/tray/system_tray_bubble.cc b/ash/system/tray/system_tray_bubble.cc
index 97500df..96e19c2 100644
--- a/ash/system/tray/system_tray_bubble.cc
+++ b/ash/system/tray/system_tray_bubble.cc
@@ -10,6 +10,7 @@
#include "ash/system/tray/system_tray_item.h"
#include "ash/system/tray/tray_bubble_wrapper.h"
#include "ash/system/tray/tray_constants.h"
+#include "ash/system/tray/tray_popup_item_container.h"
#include "base/message_loop/message_loop.h"
#include "ui/aura/window.h"
#include "ui/compositor/layer.h"
@@ -37,73 +38,6 @@ const int kDetailedBubbleMaxHeight = kTrayPopupItemHeight * 5;
// detailed view or vice versa.
const int kSwipeDelayMS = 150;
-// A view with some special behaviour for tray items in the popup:
-// - optionally changes background color on hover.
-class TrayPopupItemContainer : public views::View {
- public:
- TrayPopupItemContainer(views::View* view,
- bool change_background,
- bool draw_border)
- : hover_(false),
- change_background_(change_background) {
- set_notify_enter_exit_on_child(true);
- if (draw_border) {
- SetBorder(
- views::Border::CreateSolidSidedBorder(0, 0, 1, 0, kBorderLightColor));
- }
- views::BoxLayout* layout = new views::BoxLayout(
- views::BoxLayout::kVertical, 0, 0, 0);
- layout->SetDefaultFlex(1);
- SetLayoutManager(layout);
- SetPaintToLayer(view->layer() != NULL);
- if (view->layer())
- SetFillsBoundsOpaquely(view->layer()->fills_bounds_opaquely());
- AddChildView(view);
- SetVisible(view->visible());
- }
-
- virtual ~TrayPopupItemContainer() {}
-
- private:
- // Overridden from views::View.
- virtual void ChildVisibilityChanged(View* child) override {
- if (visible() == child->visible())
- return;
- SetVisible(child->visible());
- PreferredSizeChanged();
- }
-
- virtual void ChildPreferredSizeChanged(View* child) override {
- PreferredSizeChanged();
- }
-
- virtual void OnMouseEntered(const ui::MouseEvent& event) override {
- hover_ = true;
- SchedulePaint();
- }
-
- virtual void OnMouseExited(const ui::MouseEvent& event) override {
- hover_ = false;
- SchedulePaint();
- }
-
- virtual void OnPaintBackground(gfx::Canvas* canvas) override {
- if (child_count() == 0)
- return;
-
- views::View* view = child_at(0);
- if (!view->background()) {
- canvas->FillRect(gfx::Rect(size()), (hover_ && change_background_) ?
- kHoverBackgroundColor : kBackgroundColor);
- }
- }
-
- bool hover_;
- bool change_background_;
-
- DISALLOW_COPY_AND_ASSIGN(TrayPopupItemContainer);
-};
-
// Implicit animation observer that deletes itself and the layer at the end of
// the animation.
class AnimationObserverDeleteLayer : public ui::ImplicitAnimationObserver {
diff --git a/ash/system/tray/system_tray_unittest.cc b/ash/system/tray/system_tray_unittest.cc
index f06b648..4cfe64b 100644
--- a/ash/system/tray/system_tray_unittest.cc
+++ b/ash/system/tray/system_tray_unittest.cc
@@ -12,16 +12,21 @@
#include "ash/shelf/shelf_widget.h"
#include "ash/shell.h"
#include "ash/system/status_area_widget.h"
+#include "ash/system/tray/system_tray_bubble.h"
#include "ash/system/tray/system_tray_item.h"
#include "ash/system/tray/tray_constants.h"
+#include "ash/system/tray/tray_popup_item_container.h"
#include "ash/test/ash_test_base.h"
#include "ash/wm/window_util.h"
+#include "base/command_line.h"
#include "base/run_loop.h"
#include "base/strings/utf_string_conversions.h"
#include "ui/aura/window.h"
+#include "ui/base/ui_base_switches.h"
#include "ui/base/ui_base_types.h"
#include "ui/compositor/scoped_animation_duration_scale_mode.h"
#include "ui/events/test/event_generator.h"
+#include "ui/gfx/geometry/point.h"
#include "ui/gfx/geometry/rect.h"
#include "ui/views/controls/label.h"
#include "ui/views/layout/fill_layout.h"
@@ -157,7 +162,20 @@ class ModalWidgetDelegate : public views::WidgetDelegateView {
} // namespace
-typedef AshTestBase SystemTrayTest;
+class SystemTrayTest : public AshTestBase {
+ public:
+ SystemTrayTest() {}
+ virtual ~SystemTrayTest() {}
+
+ virtual void SetUp() OVERRIDE {
+ CommandLine::ForCurrentProcess()->AppendSwitch(
+ switches::kEnableTouchFeedback);
+ test::AshTestBase::SetUp();
+ }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(SystemTrayTest);
+};
TEST_F(SystemTrayTest, SystemTrayDefaultView) {
SystemTray* tray = GetSystemTray();
@@ -505,5 +523,58 @@ TEST_F(SystemTrayTest, SetVisibleDuringHideAnimation) {
EXPECT_EQ(1.0f, tray->layer()->GetTargetOpacity());
}
+#if defined(OS_CHROMEOS)
+// Tests that touch on an item in the system bubble triggers it to become
+// active.
+TEST_F(SystemTrayTest, TrayPopupItemContainerTouchFeedback) {
+ SystemTray* tray = GetSystemTray();
+ tray->ShowDefaultView(BUBBLE_CREATE_NEW);
+
+ TrayPopupItemContainer* view =
+ static_cast<TrayPopupItemContainer*>(tray->GetSystemBubble()->
+ bubble_view()->child_at(0));
+ EXPECT_FALSE(view->active());
+
+ ui::test::EventGenerator generator(Shell::GetPrimaryRootWindow());
+ generator.set_current_location(view->GetBoundsInScreen().CenterPoint());
+ generator.PressTouch();
+ RunAllPendingInMessageLoop();
+ EXPECT_TRUE(view->active());
+
+ generator.ReleaseTouch();
+ RunAllPendingInMessageLoop();
+ EXPECT_FALSE(view->active());
+}
+
+// Tests that touch events on an item in the system bubble cause it to stop
+// being active.
+TEST_F(SystemTrayTest, TrayPopupItemContainerTouchFeedbackCancellation) {
+ SystemTray* tray = GetSystemTray();
+ tray->ShowDefaultView(BUBBLE_CREATE_NEW);
+
+ TrayPopupItemContainer* view =
+ static_cast<TrayPopupItemContainer*>(tray->GetSystemBubble()->
+ bubble_view()->child_at(0));
+ EXPECT_FALSE(view->active());
+
+ gfx::Rect view_bounds = view->GetBoundsInScreen();
+ ui::test::EventGenerator generator(Shell::GetPrimaryRootWindow());
+ generator.set_current_location(view_bounds.CenterPoint());
+ generator.PressTouch();
+ RunAllPendingInMessageLoop();
+ EXPECT_TRUE(view->active());
+
+ gfx::Point move_point(view_bounds.x(), view_bounds.CenterPoint().y());
+ generator.MoveTouch(move_point);
+ RunAllPendingInMessageLoop();
+ EXPECT_FALSE(view->active());
+
+ generator.set_current_location(move_point);
+ generator.ReleaseTouch();
+ RunAllPendingInMessageLoop();
+ EXPECT_FALSE(view->active());
+}
+#endif // OS_CHROMEOS
+
} // namespace test
} // namespace ash
diff --git a/ash/system/tray/tray_details_view_unittest.cc b/ash/system/tray/tray_details_view_unittest.cc
index 32c859a..3430c76 100644
--- a/ash/system/tray/tray_details_view_unittest.cc
+++ b/ash/system/tray/tray_details_view_unittest.cc
@@ -8,14 +8,19 @@
#include "ash/shelf/shelf_widget.h"
#include "ash/shell.h"
#include "ash/system/status_area_widget.h"
+#include "ash/system/tray/hover_highlight_view.h"
+#include "ash/system/tray/special_popup_row.h"
#include "ash/system/tray/system_tray.h"
#include "ash/system/tray/system_tray_item.h"
#include "ash/system/tray/tray_details_view.h"
#include "ash/system/tray/view_click_listener.h"
#include "ash/test/ash_test_base.h"
+#include "base/command_line.h"
#include "base/run_loop.h"
#include "grit/ash_strings.h"
#include "ui/aura/window.h"
+#include "ui/base/ui_base_switches.h"
+#include "ui/events/test/event_generator.h"
#include "ui/views/view.h"
#include "ui/views/widget/widget.h"
@@ -31,14 +36,15 @@ SystemTray* GetSystemTray() {
class TestDetailsView : public TrayDetailsView, public ViewClickListener {
public:
- explicit TestDetailsView(SystemTrayItem* owner) : TrayDetailsView(owner) {}
-
- virtual ~TestDetailsView() {}
-
- void CreateFooterAndFocus() {
+ explicit TestDetailsView(SystemTrayItem* owner) : TrayDetailsView(owner) {
// Uses bluetooth label for testing purpose. It can be changed to any
// string_id.
CreateSpecialRow(IDS_ASH_STATUS_TRAY_BLUETOOTH, this);
+ }
+
+ virtual ~TestDetailsView() {}
+
+ void FocusFooter() {
footer()->content()->RequestFocus();
}
@@ -92,7 +98,33 @@ class TestItem : public SystemTrayItem {
} // namespace
-typedef AshTestBase TrayDetailsViewTest;
+class TrayDetailsViewTest : public AshTestBase {
+ public:
+ TrayDetailsViewTest() {}
+ virtual ~TrayDetailsViewTest() {}
+
+ HoverHighlightView* CreateAndShowHoverHighlightView() {
+ SystemTray* tray = GetSystemTray();
+ TestItem* test_item = new TestItem;
+ tray->AddTrayItem(test_item);
+ tray->ShowDefaultView(BUBBLE_CREATE_NEW);
+ RunAllPendingInMessageLoop();
+ tray->ShowDetailedView(test_item, 0, true, BUBBLE_USE_EXISTING);
+ RunAllPendingInMessageLoop();
+
+ return static_cast<HoverHighlightView*>(test_item->detailed_view()->
+ footer()->content());
+ }
+
+ virtual void SetUp() OVERRIDE {
+ CommandLine::ForCurrentProcess()->AppendSwitch(
+ switches::kEnableTouchFeedback);
+ test::AshTestBase::SetUp();
+ }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(TrayDetailsViewTest);
+};
TEST_F(TrayDetailsViewTest, TransitionToDefaultViewTest) {
SystemTray* tray = GetSystemTray();
@@ -119,7 +151,7 @@ TEST_F(TrayDetailsViewTest, TransitionToDefaultViewTest) {
// Transition back to default view, the default view of item 2 should have
// focus.
- test_item_2->detailed_view()->CreateFooterAndFocus();
+ test_item_2->detailed_view()->FocusFooter();
test_item_2->detailed_view()->TransitionToDefaultView();
RunAllPendingInMessageLoop();
@@ -143,5 +175,44 @@ TEST_F(TrayDetailsViewTest, TransitionToDefaultViewTest) {
EXPECT_FALSE(test_item_2->default_view()->HasFocus());
}
+// Tests that HoverHighlightView enters hover state in response to touch.
+TEST_F(TrayDetailsViewTest, HoverHighlightViewTouchFeedback) {
+ HoverHighlightView* view = CreateAndShowHoverHighlightView();
+ EXPECT_FALSE(view->hover());
+
+ ui::test::EventGenerator generator(Shell::GetPrimaryRootWindow());
+ generator.set_current_location(view->GetBoundsInScreen().CenterPoint());
+ generator.PressTouch();
+ RunAllPendingInMessageLoop();
+ EXPECT_TRUE(view->hover());
+
+ generator.ReleaseTouch();
+ RunAllPendingInMessageLoop();
+ EXPECT_FALSE(view->hover());
+}
+
+// Tests that touch events leaving HoverHighlightView cancel the hover state.
+TEST_F(TrayDetailsViewTest, HoverHighlightViewTouchFeedbackCancellation) {
+ HoverHighlightView* view = CreateAndShowHoverHighlightView();
+ EXPECT_FALSE(view->hover());
+
+ gfx::Rect view_bounds = view->GetBoundsInScreen();
+ ui::test::EventGenerator generator(Shell::GetPrimaryRootWindow());
+ generator.set_current_location(view_bounds.CenterPoint());
+ generator.PressTouch();
+ RunAllPendingInMessageLoop();
+ EXPECT_TRUE(view->hover());
+
+ gfx::Point move_point(view_bounds.x(), view_bounds.CenterPoint().y());
+ generator.MoveTouch(move_point);
+ RunAllPendingInMessageLoop();
+ EXPECT_FALSE(view->hover());
+
+ generator.set_current_location(move_point);
+ generator.ReleaseTouch();
+ RunAllPendingInMessageLoop();
+ EXPECT_FALSE(view->hover());
+}
+
} // namespace test
} // namespace ash
diff --git a/ash/system/tray/tray_popup_item_container.cc b/ash/system/tray/tray_popup_item_container.cc
new file mode 100644
index 0000000..9a21399
--- /dev/null
+++ b/ash/system/tray/tray_popup_item_container.cc
@@ -0,0 +1,88 @@
+// Copyright (c) 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "ash/system/tray/tray_popup_item_container.h"
+
+#include "ash/system/tray/tray_constants.h"
+#include "base/command_line.h"
+#include "ui/base/ui_base_switches_util.h"
+#include "ui/gfx/canvas.h"
+#include "ui/views/border.h"
+#include "ui/views/layout/box_layout.h"
+
+namespace ash {
+
+TrayPopupItemContainer::TrayPopupItemContainer(views::View* view,
+ bool change_background,
+ bool draw_border)
+ : active_(false),
+ change_background_(change_background) {
+ set_notify_enter_exit_on_child(true);
+ if (draw_border) {
+ SetBorder(
+ views::Border::CreateSolidSidedBorder(0, 0, 1, 0, kBorderLightColor));
+ }
+ views::BoxLayout* layout = new views::BoxLayout(
+ views::BoxLayout::kVertical, 0, 0, 0);
+ layout->SetDefaultFlex(1);
+ SetLayoutManager(layout);
+ SetPaintToLayer(view->layer() != NULL);
+ if (view->layer())
+ SetFillsBoundsOpaquely(view->layer()->fills_bounds_opaquely());
+ AddChildView(view);
+ SetVisible(view->visible());
+}
+
+TrayPopupItemContainer::~TrayPopupItemContainer() {
+}
+
+void TrayPopupItemContainer::SetActive(bool active) {
+ if (!change_background_ || active_ == active)
+ return;
+ active_ = active;
+ SchedulePaint();
+}
+
+void TrayPopupItemContainer::ChildVisibilityChanged(View* child) {
+ if (visible() == child->visible())
+ return;
+ SetVisible(child->visible());
+ PreferredSizeChanged();
+}
+
+void TrayPopupItemContainer::ChildPreferredSizeChanged(View* child) {
+ PreferredSizeChanged();
+}
+
+void TrayPopupItemContainer::OnMouseEntered(const ui::MouseEvent& event) {
+ SetActive(true);
+}
+
+void TrayPopupItemContainer::OnMouseExited(const ui::MouseEvent& event) {
+ SetActive(false);
+}
+
+void TrayPopupItemContainer::OnGestureEvent(ui::GestureEvent* event) {
+ if (!switches::IsTouchFeedbackEnabled())
+ return;
+ if (event->type() == ui::ET_GESTURE_TAP_DOWN) {
+ SetActive(true);
+ } else if (event->type() == ui::ET_GESTURE_TAP_CANCEL ||
+ event->type() == ui::ET_GESTURE_TAP) {
+ SetActive(false);
+ }
+}
+
+void TrayPopupItemContainer::OnPaintBackground(gfx::Canvas* canvas) {
+ if (child_count() == 0)
+ return;
+
+ views::View* view = child_at(0);
+ if (!view->background()) {
+ canvas->FillRect(gfx::Rect(size()), (active_) ? kHoverBackgroundColor
+ : kBackgroundColor);
+ }
+}
+
+} // namespace ash
diff --git a/ash/system/tray/tray_popup_item_container.h b/ash/system/tray/tray_popup_item_container.h
new file mode 100644
index 0000000..8992b21
--- /dev/null
+++ b/ash/system/tray/tray_popup_item_container.h
@@ -0,0 +1,51 @@
+// Copyright (c) 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef ASH_SYSTEM_TRAY_TRAY_POPUP_ITEM_CONTAINER_H_
+#define ASH_SYSTEM_TRAY_TRAY_POPUP_ITEM_CONTAINER_H_
+
+#include "ui/views/view.h"
+
+namespace ash {
+
+// A view which can optionally change the background color when a mouse is
+// hovering or a user is interacting via touch.
+class TrayPopupItemContainer : public views::View {
+ public:
+ TrayPopupItemContainer(views::View* view,
+ bool change_background,
+ bool draw_border);
+
+ virtual ~TrayPopupItemContainer();
+
+ bool active() {
+ return active_;
+ }
+
+ private:
+ // Sets whether the active background is to be used, and triggers a paint.
+ void SetActive(bool active);
+
+ // views::View:
+ virtual void ChildVisibilityChanged(views::View* child) override;
+ virtual void ChildPreferredSizeChanged(views::View* child) override;
+ virtual void OnMouseEntered(const ui::MouseEvent& event) override;
+ virtual void OnMouseExited(const ui::MouseEvent& event) override;
+ virtual void OnGestureEvent(ui::GestureEvent* event) override;
+ virtual void OnPaintBackground(gfx::Canvas* canvas) override;
+
+ // True if either a mouse is hovering over this view, or if a user has touched
+ // down.
+ bool active_;
+
+ // True if mouse hover and touch feedback can alter the background color of
+ // the container.
+ bool change_background_;
+
+ DISALLOW_COPY_AND_ASSIGN(TrayPopupItemContainer);
+};
+
+} // namespace ash
+
+#endif // ASH_SYSTEM_TRAY_TRAY_POPUP_ITEM_CONTAINER_H_