// Copyright (c) 2010 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 "chrome/browser/views/extensions/extension_shelf.h" #include #include "app/resource_bundle.h" #include "base/logging.h" #include "base/message_loop.h" #include "base/stl_util-inl.h" #include "base/string_util.h" #include "chrome/browser/browser.h" #include "chrome/browser/browser_theme_provider.h" #include "chrome/browser/extensions/extension_host.h" #include "chrome/browser/extensions/extension_process_manager.h" #include "chrome/browser/extensions/extensions_service.h" #include "chrome/browser/profile.h" #include "chrome/browser/tab_contents/tab_contents.h" #include "chrome/browser/views/extensions/extension_view.h" #include "chrome/browser/view_ids.h" #include "chrome/common/chrome_switches.h" #include "chrome/common/extensions/extension.h" #include "chrome/common/notification_service.h" #include "chrome/common/pref_names.h" #include "views/controls/label.h" #include "views/screen.h" #include "views/widget/root_view.h" namespace { // This is the slight padding that is there around the edge of the browser. This // has been determined empirically. // TODO(sidchat): Compute this value from the root view of the extension shelf. static const int kExtensionShelfPaddingOnLeft = 4; // Margins around the content. static const int kTopMarginWhenExtensionsOnTop = 1; static const int kTopMarginWhenExtensionsOnBottom = 2; static const int kBottomMargin = 2; static const int kLeftMargin = 0; static const int kRightMargin = 0; // Padding on left and right side of an extension toolstrip. static const int kToolstripPadding = 2; // Width of the toolstrip divider. static const int kToolstripDividerWidth = 2; // Preferred height of the ExtensionShelf. static const int kShelfHeight = 29; // Preferred height of the Extension shelf when only shown on the new tab page. const int kNewtabShelfHeight = 58; // How inset the extension shelf is when displayed on the new tab page. This is // in addition to the margins above. static const int kNewtabHorizontalPadding = 8; static const int kNewtabVerticalPadding = 12; // We need an extra margin to the left of all the toolstrips in detached mode, // so that the first toolstrip doesn't look so squished against the rounded // corners of the extension shelf. static const int kNewtabExtraHorMargin = 2; static const int kNewtabExtraVerMargin = 2; // Padding for the title inside the handle. static const int kHandlePadding = 4; // Inset for the extension view when displayed either dragging or expanded. static const int kWindowInset = 1; // Delays for showing and hiding the shelf handle. static const int kShowDelayMs = 500; static const int kHideDelayMs = 300; } // namespace // A view that holds the place for a toolstrip in the shelf while the toolstrip // is being dragged or moved. // TODO(erikkay) this should draw a dimmed out version of the toolstrip. class ExtensionShelf::PlaceholderView : public views::View { public: PlaceholderView() {} void SetWidth(int width) { SetBounds(x(), y(), width, height()); PreferredSizeChanged(); } // ExtensionShelf resizes its views to their preferred size at layout, // so just always prefer the current size. gfx::Size GetPreferredSize() { return size(); } private: DISALLOW_COPY_AND_ASSIGN(PlaceholderView); }; // A wrapper class for the ExtensionHost displayed as a toolstrip. // The class itself also acts as the View for the handle of the toolstrip // it represents. class ExtensionShelf::Toolstrip : public views::View, public BrowserBubble::Delegate, public AnimationDelegate { public: Toolstrip(ExtensionShelf* shelf, ExtensionHost* host, const Extension::ToolstripInfo& info); virtual ~Toolstrip(); // Convenience to calculate just the size of the handle. gfx::Size GetHandlePreferredSize(); // View methods: virtual void Paint(gfx::Canvas* canvas); virtual gfx::Size GetPreferredSize(); virtual void Layout(); virtual void OnMouseEntered(const views::MouseEvent& event); virtual void OnMouseExited(const views::MouseEvent& event); virtual bool OnMousePressed(const views::MouseEvent& event); virtual bool OnMouseDragged(const views::MouseEvent& event); virtual void OnMouseReleased(const views::MouseEvent& event, bool canceled); virtual bool IsFocusable() const { return true; } virtual void ChildPreferredSizeChanged(View* child); // Adjust the size and position of the window/handle. void LayoutWindow(); // Is the toolstrip window visible (not necessarily the handle). bool window_visible() { return (window_.get() && window_->visible()); } // Is the handle for this toolstrip currently visible. bool handle_visible() { return (window_visible() && handle_visible_); } // Is the toolstrip opened in expanded mode. bool expanded() { return expanded_; } // Is the toolstrip being dragged. bool dragging() { return dragging_; } // Accessors for the host and its view. ExtensionHost* host() const { return host_; } ExtensionView* view() const { return host_->view(); } // The view that's currently displayed in the shelf. This could be // either the ExtensionView or |placeholder_view_| depending on the // current state. View* GetShelfView() const { if (placeholder_view_) return placeholder_view_; return view(); } // Detaching the toolstrip from the shelf means that the ExtensionView is // displayed inside of the BrowserBubble rather than the shelf. This may // still visually appear to be part of the shelf (expanded) or completely // separate (dragging). // If |browser| is true, it also will detach/attach to the browser window. void DetachFromShelf(bool browser); void AttachToShelf(bool browser); // Show / Hide the shelf handle. void ShowShelfHandle(); void DoShowShelfHandle(); void HideShelfHandle(int delay_ms); void DoHideShelfHandle(); void StopHandleTimer(); void HideWindow(); void ShowWindow(); // Expand / Collapse void Expand(int height, const GURL& url); void Collapse(const GURL& url); // BrowserBubble::Delegate virtual void BubbleBrowserWindowMoved(BrowserBubble* bubble); virtual void BubbleBrowserWindowClosing(BrowserBubble* bubble); // AnimationDelegate virtual void AnimationProgressed(const Animation* animation); virtual void AnimationEnded(const Animation* animation); private: // The actual renderer that this toolstrip contains. ExtensionHost* host_; // Manifest definition of this toolstrip. Extension::ToolstripInfo info_; // A window that can be logically attached to the browser window. This is // used for two purposes: a handle that sits above the toolstrip when it's // collapsed and not dragging, and as a container for the toolstrip when it's // dragging or expanded. scoped_ptr window_; // Used for drawing the name of the extension in the handle. scoped_ptr title_; // Pointer back to the containing shelf. ExtensionShelf* shelf_; // When dragging, a placeholder view is put into the shelf to hold space // for the ExtensionView. This view is parent owned when it's in the view // hierarchy, so there's no ownership issues here. PlaceholderView* placeholder_view_; // Current state of the toolstrip, currently can't both be expanded and // dragging. // TODO(erikkay) Support dragging while expanded. bool dragging_; bool expanded_; bool handle_visible_; // The target expanded height of the toolstrip (used for animation). int expanded_height_; // If dragging, where did the drag start from. gfx::Point initial_drag_location_; // We have to remember the initial drag point in screen coordinates, because // later, when the toolstrip is being dragged around, there is no good way of // computing the screen coordinates given the initial drag view coordinates. gfx::Point initial_drag_screen_point_; // Timers for tracking mouse hovering. ScopedRunnableMethodFactory timer_factory_; // Animate opening and closing the mole. scoped_ptr mole_animation_; DISALLOW_COPY_AND_ASSIGN(Toolstrip); }; ExtensionShelf::Toolstrip::Toolstrip(ExtensionShelf* shelf, ExtensionHost* host, const Extension::ToolstripInfo& info) : host_(host), info_(info), shelf_(shelf), placeholder_view_(NULL), dragging_(false), expanded_(false), handle_visible_(false), expanded_height_(0), ALLOW_THIS_IN_INITIALIZER_LIST(timer_factory_(this)) { DCHECK(host->view()); // We're owned by shelf_, not the bubble that we get inserted in and out of. set_parent_owned(false); mole_animation_.reset(new SlideAnimation(this)); std::wstring name = UTF8ToWide(host_->extension()->name()); // |title_| isn't actually put in the view hierarchy. We just use it // to draw in place. The reason for this is so that we can properly handle // the various mouse events necessary for hovering and dragging. ResourceBundle& rb = ResourceBundle::GetSharedInstance(); title_.reset(new views::Label(name, rb.GetFont(ResourceBundle::BaseFont))); title_->SetBounds(kHandlePadding, kHandlePadding, 100, 100); title_->SizeToPreferredSize(); SizeToPreferredSize(); } ExtensionShelf::Toolstrip::~Toolstrip() { if (window_.get() && window_->attached()) window_->DetachFromBrowser(); } void ExtensionShelf::Toolstrip::Paint(gfx::Canvas* canvas) { // Paint the background. SkColor theme_toolbar_color = shelf_->GetThemeProvider()->GetColor(BrowserThemeProvider::COLOR_TOOLBAR); canvas->FillRectInt(theme_toolbar_color, 0, 0, width(), height()); // Paints the handle for the toolstrip (only called on mouse-hover). SkColor border_color = ResourceBundle::toolbar_separator_color; canvas->FillRectInt(border_color, 0, 0, width(), 1); canvas->FillRectInt(border_color, 0, 0, 1, height() - 1); canvas->FillRectInt(border_color, width() - 1, 0, 1, height() - 1); int ext_width = view()->width() + kToolstripPadding + kToolstripDividerWidth; if (ext_width < width()) { canvas->FillRectInt(border_color, ext_width, height() - 1, width() - ext_width, 1); } if (handle_visible_) { // Draw the title using a Label as a stamp. // See constructor for comment about this. title_->ProcessPaint(canvas); if (dragging_) { // When we're dragging, draw the bottom border. canvas->FillRectInt(border_color, 0, height() - 1, width(), 1); } } } gfx::Size ExtensionShelf::Toolstrip::GetHandlePreferredSize() { gfx::Size sz; if (handle_visible_) { sz = title_->GetPreferredSize(); sz.set_width(std::max(view()->width(), sz.width())); sz.Enlarge(2 + kHandlePadding * 2, kHandlePadding * 2); } return sz; } gfx::Size ExtensionShelf::Toolstrip::GetPreferredSize() { gfx::Size sz; if (handle_visible_) sz = GetHandlePreferredSize(); if (view()->GetParent() == this) { gfx::Size extension_size = view()->GetPreferredSize(); sz.Enlarge(0, extension_size.height()); sz.set_width(extension_size.width()); } // The view is inset slightly when displayed in the window. sz.Enlarge(kWindowInset * 2, kWindowInset * 2); return sz; } void ExtensionShelf::Toolstrip::Layout() { if (view()->GetParent() == this) { int view_y = kWindowInset; if (handle_visible_) view_y += GetHandlePreferredSize().height(); view()->SetBounds(kWindowInset, view_y, view()->width(), view()->height()); } } void ExtensionShelf::Toolstrip::OnMouseEntered(const views::MouseEvent& event) { if (dragging_) return; ShowShelfHandle(); } void ExtensionShelf::Toolstrip::OnMouseExited(const views::MouseEvent& event) { if (dragging_) return; HideShelfHandle(kHideDelayMs); } bool ExtensionShelf::Toolstrip::OnMousePressed(const views::MouseEvent& event) { initial_drag_location_ = event.location(); initial_drag_screen_point_ = views::Screen::GetCursorScreenPoint(); return true; } bool ExtensionShelf::Toolstrip::OnMouseDragged(const views::MouseEvent& event) { if (expanded_) { // Do nothing for now. } else if (!dragging_) { int y_delta = abs(initial_drag_location_.y() - event.location().y()); int x_delta = abs(initial_drag_location_.x() - event.location().x()); if (y_delta > GetVerticalDragThreshold() || x_delta > GetHorizontalDragThreshold()) { dragging_ = true; StopHandleTimer(); DetachFromShelf(true); } } else { // When freely dragging a window, you can really only trust the // actual screen point. Local coordinate conversions don't work. gfx::Point screen = views::Screen::GetCursorScreenPoint(); // However, the handle is actually a child of the browser window // so we need to convert it back to local coordinates. gfx::Point origin(0, 0); views::View::ConvertPointToScreen(shelf_->GetRootView(), &origin); int screen_x = screen.x() - initial_drag_location_.x() - origin.x(); // Restrict the horizontal and vertical motions of the toolstrip so that it // cannot be dragged out of the extension shelf. If the extension shelf is // on the top along with the bookmark bar, the toolstrip cannot be dragged // into the space allocated for bookmarks. The toolstrip cannot be dragged // out of the browser window. if (screen_x < kExtensionShelfPaddingOnLeft) { screen_x = kExtensionShelfPaddingOnLeft; } else if (screen_x > shelf_->width() - width() + kExtensionShelfPaddingOnLeft) { screen_x = shelf_->width() - width() + kExtensionShelfPaddingOnLeft; } screen.set_x(screen_x); screen.set_y(initial_drag_screen_point_.y() - origin.y() - initial_drag_location_.y()); // TODO(erikkay) as this gets dragged around, update the placeholder view // on the shelf to show where it will get dropped to. window_->MoveTo(screen.x(), screen.y()); } return true; } void ExtensionShelf::Toolstrip::OnMouseReleased(const views::MouseEvent& event, bool canceled) { StopHandleTimer(); if (dragging_) { // Drop the toolstrip roughly where it is now. views::View::OnMouseReleased(event, canceled); dragging_ = false; // |this| and |shelf_| are in different view hierarchies, so we need to // convert to screen coordinates and back again to map locations. gfx::Point loc = event.location(); View::ConvertPointToScreen(this, &loc); View::ConvertPointToView(NULL, shelf_, &loc); shelf_->DropExtension(this, loc, canceled); AttachToShelf(true); } else if (!canceled) { // Toggle mole to either expanded or collapsed. // TODO(erikkay) If there's no valid URL in the manifest, should we // post an event to the toolstrip in this case? if (expanded_) { if (info_.toolstrip.is_valid()) shelf_->CollapseToolstrip(host_, info_.toolstrip); } else { if (info_.mole.is_valid()) shelf_->ExpandToolstrip(host_, info_.mole, info_.mole_height); } } } void ExtensionShelf::Toolstrip::LayoutWindow() { if (!window_visible() && !handle_visible_ && !expanded_) return; if (!window_.get()) { window_.reset(new BrowserBubble(this, shelf_->GetWidget(), gfx::Point(0, 0), false)); // Do not add a drop-shadow. window_->set_delegate(this); } gfx::Size window_size = GetPreferredSize(); if (mole_animation_->is_animating()) { // We only want to animate the body of the mole window. When we're // expanding, this is everything except for the handle. When we're // collapsing, this is everything except for the handle and the toolstrip. // We subtract this amount from the target height, figure out the step in // the animation from the rest, and then add it back in. int window_offset = shelf_->height(); if (!mole_animation_->IsShowing()) window_offset += GetPreferredSize().height(); else window_offset += GetHandlePreferredSize().height(); int h = expanded_height_ - window_offset; window_size.set_height(window_offset + static_cast(h * mole_animation_->GetCurrentValue())); } else if (!expanded_ && !dragging_) { window_size.set_height(GetHandlePreferredSize().height()); } // Now figure out where to place the window on the screen. Since it's a top- // level widget, we need to do some coordinate conversion to get this right. gfx::Point origin(-kToolstripPadding, 0); if (expanded_ || mole_animation_->is_animating()) { origin.set_y(GetShelfView()->height() - window_size.height()); views::View::ConvertPointToView(GetShelfView(), shelf_->GetRootView(), &origin); } else { origin.set_y(-(window_size.height() + kToolstripPadding - 1)); views::View::ConvertPointToWidget(view(), &origin); } SetBounds(0, 0, window_size.width(), window_size.height()); window_->SetBounds(origin.x(), origin.y(), window_size.width(), window_size.height()); } void ExtensionShelf::Toolstrip::ChildPreferredSizeChanged(View* child) { if (child == view()) { child->SizeToPreferredSize(); Layout(); if (window_visible()) LayoutWindow(); if (expanded_) { placeholder_view_->SetWidth(child->width()); shelf_->Layout(); } } } void ExtensionShelf::Toolstrip::BubbleBrowserWindowMoved( BrowserBubble* bubble) { if (!expanded_) HideWindow(); } void ExtensionShelf::Toolstrip::BubbleBrowserWindowClosing( BrowserBubble* bubble) { HideWindow(); } void ExtensionShelf::Toolstrip::AnimationProgressed( const Animation* animation) { LayoutWindow(); } void ExtensionShelf::Toolstrip::AnimationEnded(const Animation* animation) { if (window_visible()) LayoutWindow(); if (!expanded_) { AttachToShelf(false); HideShelfHandle(kHideDelayMs); } } void ExtensionShelf::Toolstrip::DetachFromShelf(bool browserDetach) { DCHECK(window_.get()); DCHECK(!placeholder_view_); if (browserDetach && window_->attached()) window_->DetachFromBrowser(); // Construct a placeholder view to replace the view. placeholder_view_ = new PlaceholderView(); placeholder_view_->SetBounds(view()->bounds()); shelf_->AddChildView(placeholder_view_); AddChildView(view()); SizeToPreferredSize(); window_->ResizeToView(); Layout(); } void ExtensionShelf::Toolstrip::AttachToShelf(bool browserAttach) { DCHECK(window_.get()); DCHECK(placeholder_view_); if (browserAttach && !window_->attached()) window_->AttachToBrowser(); // Move the view back into the shelf and remove the old placeholder. shelf_->AddChildView(view()); // The size of the view may have changed, so just set the position. view()->SetX(placeholder_view_->x()); view()->SetY(placeholder_view_->y()); // Remove the old placeholder. shelf_->RemoveChildView(placeholder_view_); delete placeholder_view_; placeholder_view_ = NULL; SizeToPreferredSize(); Layout(); shelf_->Layout(); } void ExtensionShelf::Toolstrip::DoShowShelfHandle() { if (!handle_visible()) { handle_visible_ = true; // Make sure the text color for the title matches the theme colors. title_->SetColor( shelf_->GetThemeProvider()->GetColor( BrowserThemeProvider::COLOR_BOOKMARK_TEXT)); ShowWindow(); } } void ExtensionShelf::Toolstrip::HideWindow() { if (!window_visible()) return; handle_visible_ = false; window_->Hide(); if (expanded_) { if (info_.toolstrip.is_valid()) shelf_->CollapseToolstrip(host_, info_.toolstrip); else shelf_->CollapseToolstrip(host_, GURL()); } if (window_->attached()) window_->DetachFromBrowser(); window_.reset(NULL); shelf_->Layout(); } void ExtensionShelf::Toolstrip::ShowWindow() { DCHECK(handle_visible_ || expanded_); LayoutWindow(); if (!window_visible()) window_->Show(false); // |false| means show, but don't activate. } void ExtensionShelf::Toolstrip::DoHideShelfHandle() { if (!handle_visible()) return; handle_visible_ = false; if (expanded_) { LayoutWindow(); Layout(); } else { HideWindow(); } } void ExtensionShelf::Toolstrip::StopHandleTimer() { if (!timer_factory_.empty()) timer_factory_.RevokeAll(); } void ExtensionShelf::Toolstrip::Expand(int height, const GURL& url) { DCHECK(!expanded_); expanded_ = true; ShowWindow(); bool navigate = (!url.is_empty() && url != host_->GetURL()); if (navigate) host_->NavigateToURL(url); StopHandleTimer(); DetachFromShelf(false); mole_animation_->Show(); gfx::Size extension_size = view()->GetPreferredSize(); extension_size.set_height(height); view()->SetPreferredSize(extension_size); expanded_height_ = GetPreferredSize().height(); // This is to prevent flickering as the page loads and lays out. // Once the navigation is finished, ExtensionView will wind up setting // visibility to true. if (navigate) view()->SetVisible(false); } void ExtensionShelf::Toolstrip::Collapse(const GURL& url) { DCHECK(expanded_); expanded_ = false; if (window_visible()) mole_animation_->Hide(); gfx::Size extension_size = view()->GetPreferredSize(); extension_size.set_height( kShelfHeight - (shelf_->top_margin() + kBottomMargin)); view()->SetPreferredSize(extension_size); if (!url.is_empty() && url != host_->GetURL()) { host_->NavigateToURL(url); // This is to prevent flickering as the page loads and lays out. // Once the navigation is finished, ExtensionView will wind up setting // visibility to true. view()->SetVisible(false); } if (!window_visible()) AnimationEnded(NULL); } void ExtensionShelf::Toolstrip::ShowShelfHandle() { StopHandleTimer(); if (handle_visible()) return; MessageLoop::current()->PostDelayedTask(FROM_HERE, timer_factory_.NewRunnableMethod( &ExtensionShelf::Toolstrip::DoShowShelfHandle), kShowDelayMs); } void ExtensionShelf::Toolstrip::HideShelfHandle(int delay_ms) { StopHandleTimer(); if (!handle_visible() || dragging_ || mole_animation_->is_animating()) return; if (delay_ms) { MessageLoop::current()->PostDelayedTask(FROM_HERE, timer_factory_.NewRunnableMethod( &ExtensionShelf::Toolstrip::DoHideShelfHandle), delay_ms); } else { DoHideShelfHandle(); } } //////////////////////////////////////////////////////////////////////////////// ExtensionShelf::ExtensionShelf(Browser* browser) : background_needs_repaint_(true), browser_(browser), model_(browser->extension_shelf_model()), fullscreen_(false) { SetID(VIEW_ID_DEV_EXTENSION_SHELF); top_margin_ = kTopMarginWhenExtensionsOnBottom; model_->AddObserver(this); LoadFromModel(); EnableCanvasFlippingForRTLUI(true); registrar_.Add(this, NotificationType::EXTENSION_SHELF_VISIBILITY_PREF_CHANGED, NotificationService::AllSources()); size_animation_.reset(new SlideAnimation(this)); if (IsAlwaysShown()) size_animation_->Reset(1); else size_animation_->Reset(0); } ExtensionShelf::~ExtensionShelf() { if (model_) { int count = model_->count(); for (int i = 0; i < count; ++i) { delete ToolstripAtIndex(i); model_->SetToolstripDataAt(i, NULL); } model_->RemoveObserver(this); } } void ExtensionShelf::PaintChildren(gfx::Canvas* canvas) { InitBackground(canvas); int max_x = width(); if (IsDetached()) max_x -= 2 * kNewtabHorizontalPadding; // Draw vertical dividers between Toolstrip items in the Extension shelf. int count = GetChildViewCount(); for (int i = 0; i < count; ++i) { int right = GetChildViewAt(i)->bounds().right() + kToolstripPadding; if (right >= max_x) break; int vertical_padding = IsDetached() ? (height() - kShelfHeight) / 2 : 1; DetachableToolbarView::PaintVerticalDivider( canvas, right, height(), vertical_padding, DetachableToolbarView::kEdgeDividerColor, DetachableToolbarView::kMiddleDividerColor, GetThemeProvider()->GetColor(BrowserThemeProvider::COLOR_TOOLBAR)); } } // static void ExtensionShelf::ToggleWhenExtensionShelfVisible(Profile* profile) { PrefService* prefs = profile->GetPrefs(); const bool always_show = !prefs->GetBoolean(prefs::kShowExtensionShelf); // The user changed when the Extension Shelf is shown, update the // preferences. prefs->SetBoolean(prefs::kShowExtensionShelf, always_show); prefs->ScheduleSavePersistentPrefs(); // And notify the notification service. Source source(profile); NotificationService::current()->Notify( NotificationType::EXTENSION_SHELF_VISIBILITY_PREF_CHANGED, source, NotificationService::NoDetails()); } gfx::Size ExtensionShelf::GetPreferredSize() { return LayoutItems(true); } void ExtensionShelf::ChildPreferredSizeChanged(View* child) { PreferredSizeChanged(); } void ExtensionShelf::Layout() { LayoutItems(false); } void ExtensionShelf::OnMouseEntered(const views::MouseEvent& event) { } void ExtensionShelf::OnMouseExited(const views::MouseEvent& event) { } bool ExtensionShelf::GetAccessibleRole(AccessibilityTypes::Role* role) { DCHECK(role); *role = AccessibilityTypes::ROLE_TOOLBAR; return true; } void ExtensionShelf::ThemeChanged() { // Refresh the CSS to update toolstrip text colors from theme. int count = model_->count(); for (int i = 0; i < count; ++i) ToolstripAtIndex(i)->view()->host()->InsertThemedToolstripCSS(); Layout(); } void ExtensionShelf::ToolstripInsertedAt(ExtensionHost* host, int index) { model_->SetToolstripDataAt(index, new Toolstrip(this, host, model_->ToolstripAt(index).info)); bool had_views = GetChildViewCount() > 0; ExtensionView* view = host->view(); AddChildView(view); view->SetContainer(this); if (!had_views) PreferredSizeChanged(); Layout(); } void ExtensionShelf::ToolstripRemovingAt(ExtensionHost* host, int index) { // Delete the Toolstrip view and remove it from the model. Toolstrip* toolstrip = ToolstripAtIndex(index); View* view = toolstrip->GetShelfView(); RemoveChildView(view); delete toolstrip; model_->SetToolstripDataAt(index, NULL); // Technically, the toolstrip is still in the model at this point, but // the Layout code handles this case. Layout(); } void ExtensionShelf::ToolstripDraggingFrom(ExtensionHost* host, int index) { } void ExtensionShelf::ToolstripMoved(ExtensionHost* host, int from_index, int to_index) { Layout(); } void ExtensionShelf::ToolstripChanged(ExtensionShelfModel::iterator toolstrip) { Toolstrip* t = static_cast(toolstrip->data); if (toolstrip->height > 0) { if (!t->expanded()) { t->Expand(toolstrip->height, toolstrip->url); } } else if (t->expanded()) { t->Collapse(toolstrip->url); } } void ExtensionShelf::ExtensionShelfEmpty() { PreferredSizeChanged(); } void ExtensionShelf::ShelfModelReloaded() { // None of the child views are parent owned, so nothing is being leaked here. RemoveAllChildViews(false); LoadFromModel(); } void ExtensionShelf::ShelfModelDeleting() { int count = model_->count(); for (int i = 0; i < count; ++i) { delete ToolstripAtIndex(i); model_->SetToolstripDataAt(i, NULL); } model_->RemoveObserver(this); model_ = NULL; } void ExtensionShelf::AnimationProgressed(const Animation* animation) { if (browser_) browser_->ExtensionShelfSizeChanged(); } void ExtensionShelf::AnimationEnded(const Animation* animation) { if (browser_) browser_->ExtensionShelfSizeChanged(); Layout(); } void ExtensionShelf::Observe(NotificationType type, const NotificationSource& source, const NotificationDetails& details) { switch (type.value) { case NotificationType::EXTENSION_SHELF_VISIBILITY_PREF_CHANGED: { if (fullscreen_) break; if (IsAlwaysShown()) size_animation_->Show(); else size_animation_->Hide(); break; } default: NOTREACHED(); break; } } void ExtensionShelf::OnExtensionMouseEvent(ExtensionView* view) { Toolstrip *toolstrip = ToolstripForView(view); if (toolstrip) toolstrip->ShowShelfHandle(); } void ExtensionShelf::OnExtensionMouseLeave(ExtensionView* view) { Toolstrip *toolstrip = ToolstripForView(view); if (toolstrip) toolstrip->HideShelfHandle(kHideDelayMs); } void ExtensionShelf::DropExtension(Toolstrip* toolstrip, const gfx::Point& pt, bool cancel) { Toolstrip* dest_toolstrip = ToolstripAtX(pt.x()); if (!dest_toolstrip) { if (pt.x() > 0) dest_toolstrip = ToolstripAtIndex(model_->count() - 1); else dest_toolstrip = ToolstripAtIndex(0); } if (toolstrip == dest_toolstrip) return; int from = model_->IndexOfHost(toolstrip->host()); int to = model_->IndexOfHost(dest_toolstrip->host()); DCHECK(from != to); model_->MoveToolstripAt(from, to); } void ExtensionShelf::ExpandToolstrip(ExtensionHost* host, const GURL& url, int height) { ExtensionShelfModel::iterator toolstrip = model_->ToolstripForHost(host); model_->ExpandToolstrip(toolstrip, url, height); } void ExtensionShelf::CollapseToolstrip(ExtensionHost* host, const GURL& url) { ExtensionShelfModel::iterator toolstrip = model_->ToolstripForHost(host); model_->CollapseToolstrip(toolstrip, url); } void ExtensionShelf::InitBackground(gfx::Canvas* canvas) { if (!background_needs_repaint_) return; // Capture a background bitmap to give to the toolstrips. SkRect background_rect = { SkIntToScalar(0), SkIntToScalar(0), SkIntToScalar(width()), SkIntToScalar(height()) }; // Tell all extension views about the new background. int count = model_->count(); for (int i = 0; i < count; ++i) { ExtensionView* view = ToolstripAtIndex(i)->view(); const SkBitmap& background = canvas->getDevice()->accessBitmap(false); SkRect mapped_subset = background_rect; gfx::Rect view_bounds = view->bounds(); mapped_subset.offset(SkIntToScalar(view_bounds.x()), SkIntToScalar(view_bounds.y())); bool result = canvas->getTotalMatrix().mapRect(&mapped_subset); DCHECK(result); SkIRect isubset; mapped_subset.round(&isubset); SkBitmap subset_bitmap; // This will create another bitmap that just references pixels in the // actual bitmap. result = background.extractSubset(&subset_bitmap, isubset); if (!result) return; // We do a deep copy because extractSubset() returns a bitmap that // references pixels in the original one and we want to actually make a // smaller copy that will have a long lifetime. SkBitmap smaller_copy; if (!subset_bitmap.copyTo(&smaller_copy, SkBitmap::kARGB_8888_Config)) return; DCHECK(smaller_copy.readyToDraw()); view->SetBackground(smaller_copy); } background_needs_repaint_ = false; } ExtensionShelf::Toolstrip* ExtensionShelf::ToolstripAtX(int x) { int count = model_->count(); if (count == 0) return NULL; if (x < 0) return NULL; for (int i = 0; i < count; ++i) { Toolstrip* toolstrip = ToolstripAtIndex(i); View* view = toolstrip->GetShelfView(); int x_mirrored = view->GetRootView()->MirroredXCoordinateInsideView(x); if (x_mirrored > view->x() + view->width() + kToolstripPadding) continue; return toolstrip; } return NULL; } ExtensionShelf::Toolstrip* ExtensionShelf::ToolstripAtIndex(int index) { return static_cast(model_->ToolstripAt(index).data); } ExtensionShelf::Toolstrip* ExtensionShelf::ToolstripForView( ExtensionView* view) { int count = model_->count(); for (int i = 0; i < count; ++i) { Toolstrip* toolstrip = ToolstripAtIndex(i); if (view == toolstrip->view()) return toolstrip; } return NULL; } void ExtensionShelf::LoadFromModel() { int count = model_->count(); for (int i = 0; i < count; ++i) ToolstripInsertedAt(model_->ToolstripAt(i).host, i); } gfx::Size ExtensionShelf::LayoutItems(bool compute_bounds_only) { if (!GetParent() || !model_ || !model_->count()) return gfx::Size(0, 0); gfx::Size prefsize; int x = kLeftMargin; int y = top_margin_; int content_height = kShelfHeight - top_margin_ - kBottomMargin; int max_x = width() - kRightMargin; if (OnNewTabPage()) { double current_state = 1 - size_animation_->GetCurrentValue(); x += static_cast(static_cast (kNewtabHorizontalPadding + kNewtabExtraHorMargin) * current_state); y += static_cast(static_cast (kNewtabVerticalPadding + kNewtabExtraVerMargin) * current_state); max_x -= static_cast(static_cast (2 * kNewtabHorizontalPadding) * current_state); } int count = model_->count(); for (int i = 0; i < count; ++i) { x += kToolstripPadding; // Left padding. Toolstrip* toolstrip = ToolstripAtIndex(i); if (!toolstrip) // Can be NULL while in the process of removing. continue; View* view = toolstrip->GetShelfView(); gfx::Size pref = view->GetPreferredSize(); // |next_x| is the x value for where the next toolstrip in the list will be. int next_x = x + pref.width() + kToolstripPadding; // Right padding. if (!compute_bounds_only) { bool clipped = next_x >= max_x; if (clipped) pref.set_width(std::max(0, max_x - x)); if (view == toolstrip->view()) toolstrip->view()->SetIsClipped(clipped); view->SetBounds(x, y, pref.width(), content_height); view->Layout(); if (toolstrip->window_visible()) toolstrip->LayoutWindow(); } x = next_x + kToolstripDividerWidth; } if (!compute_bounds_only) { background_needs_repaint_ = true; SchedulePaint(); } else { if (OnNewTabPage()) { prefsize.set_height(kShelfHeight + static_cast(static_cast (kNewtabShelfHeight - kShelfHeight) * (1 - size_animation_->GetCurrentValue()))); } else { prefsize.set_height(static_cast(static_cast(kShelfHeight) * size_animation_->GetCurrentValue())); } x += kRightMargin; prefsize.set_width(x); } return prefsize; } bool ExtensionShelf::IsDetached() const { return OnNewTabPage() && (size_animation_->GetCurrentValue() != 1); } bool ExtensionShelf::IsAlwaysShown() const { Profile* profile = browser_->profile(); return profile->GetPrefs()->GetBoolean(prefs::kShowExtensionShelf); } bool ExtensionShelf::OnNewTabPage() const { return (browser_ && browser_->GetSelectedTabContents() && browser_->GetSelectedTabContents()->IsExtensionShelfAlwaysVisible()); } void ExtensionShelf::OnFullscreenToggled(bool fullscreen) { if (fullscreen == fullscreen_) return; fullscreen_ = fullscreen; if (!IsAlwaysShown()) return; size_animation_->Reset(fullscreen ? 0 : 1); }