diff options
Diffstat (limited to 'chrome/browser/views/extensions/extension_shelf.cc')
-rw-r--r-- | chrome/browser/views/extensions/extension_shelf.cc | 589 |
1 files changed, 589 insertions, 0 deletions
diff --git a/chrome/browser/views/extensions/extension_shelf.cc b/chrome/browser/views/extensions/extension_shelf.cc new file mode 100644 index 0000000..19e15b4 --- /dev/null +++ b/chrome/browser/views/extensions/extension_shelf.cc @@ -0,0 +1,589 @@ +// Copyright (c) 2009 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 <algorithm> + +#include "app/resource_bundle.h" +#include "base/logging.h" +#include "base/message_loop.h" +#include "base/string_util.h" +#include "chrome/browser/browser.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/views/extensions/extension_view.h" +#include "chrome/common/extensions/extension.h" +#include "chrome/common/notification_service.h" +#include "skia/ext/skia_utils.h" +#include "views/controls/label.h" +#include "views/screen.h" +#include "views/widget/root_view.h" + +namespace { + +// Margins around the content. +static const int kTopMargin = 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; + +// Colors for the ExtensionShelf. +static const SkColor kBackgroundColor = SkColorSetRGB(230, 237, 244); +static const SkColor kBorderColor = SkColorSetRGB(201, 212, 225); +static const SkColor kDividerHighlightColor = SkColorSetRGB(247, 250, 253); + +// Text colors for the handle +static const SkColor kHandleTextColor = SkColorSetRGB(6, 45, 117); +static const SkColor kHandleTextHighlightColor = + SkColorSetARGB(200, 255, 255, 255); + +// Handle padding +static const int kHandlePadding = 4; + +// TODO(erikkay) convert back to a gradient when Glen figures out the +// specs. +// static const SkColor kBackgroundColor = SkColorSetRGB(237, 244, 252); +// static const SkColor kTopGradientColor = SkColorSetRGB(222, 234, 248); + +// Delays for showing and hiding the shelf handle. +static const int kHideDelayMs = 500; + +} // namespace + + +// A small handle that is used for dragging or otherwise interacting with an +// extension toolstrip. +class ExtensionShelfHandle : public views::View { + public: + explicit ExtensionShelfHandle(ExtensionShelf* shelf); + + // The ExtensionView that the handle is attached to. + void SetExtensionView(ExtensionView* v); + + // View + 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; } + + private: + ExtensionShelf* shelf_; + ExtensionView* extension_view_; + scoped_ptr<views::Label> title_; + bool dragging_; + gfx::Point initial_drag_location_; + + DISALLOW_COPY_AND_ASSIGN(ExtensionShelfHandle); +}; + +ExtensionShelfHandle::ExtensionShelfHandle(ExtensionShelf* shelf) + : shelf_(shelf), extension_view_(NULL), dragging_(false) { + ResourceBundle& rb = ResourceBundle::GetSharedInstance(); + + // |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. + title_.reset(new views::Label(L"", rb.GetFont(ResourceBundle::BaseFont))); + title_->SetColor(kHandleTextColor); + title_->SetDrawHighlighted(true); + title_->SetHighlightColor(kHandleTextHighlightColor); + title_->SetBounds(kHandlePadding, kHandlePadding, 100, 100); + title_->SizeToPreferredSize(); +} + +void ExtensionShelfHandle::SetExtensionView(ExtensionView* v) { + DCHECK(v->extension()); + extension_view_ = v; + if (!extension_view_->extension()) + return; + title_->SetText(UTF8ToWide(extension_view_->extension()->name())); + title_->SizeToPreferredSize(); + SizeToPreferredSize(); +} + +void ExtensionShelfHandle::Paint(gfx::Canvas* canvas) { + canvas->FillRectInt(kBackgroundColor, 0, 0, width(), height()); + canvas->FillRectInt(kBorderColor, 0, 0, width(), 1); + canvas->FillRectInt(kBorderColor, 0, 0, 1, height() - 1); + canvas->FillRectInt(kBorderColor, width() - 1, 0, 1, height() - 1); + int ext_width = extension_view_->width() + kToolstripPadding + + kToolstripDividerWidth; + if (ext_width < width()) { + canvas->FillRectInt(kBorderColor, ext_width, height() - 1, + width() - ext_width, 1); + } + + // 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(kBorderColor, 0, height() - 1, width(), 1); + } +} + +gfx::Size ExtensionShelfHandle::GetPreferredSize() { + gfx::Size sz = title_->GetPreferredSize(); + if (extension_view_) { + int width = std::max(extension_view_->width() + 2, sz.width()); + sz.set_width(width); + } + sz.Enlarge(kHandlePadding * 2, kHandlePadding * 2); + if (dragging_) { + gfx::Size extension_size = extension_view_->GetPreferredSize(); + sz.Enlarge(0, extension_size.height() + 2); + } + return sz; +} + +void ExtensionShelfHandle::Layout() { + if (dragging_) { + int y = title_->bounds().bottom() + kHandlePadding + 1; + extension_view_->SetBounds(1, + y, + extension_view_->width(), + extension_view_->height()); + } +} + +void ExtensionShelfHandle::OnMouseEntered(const views::MouseEvent& event) { + DCHECK(extension_view_); + shelf_->OnExtensionMouseEvent(extension_view_); +} + +void ExtensionShelfHandle::OnMouseExited(const views::MouseEvent& event) { + DCHECK(extension_view_); + shelf_->OnExtensionMouseLeave(extension_view_); +} + +bool ExtensionShelfHandle::OnMousePressed(const views::MouseEvent& event) { + initial_drag_location_ = event.location(); + return true; +} + +bool ExtensionShelfHandle::OnMouseDragged(const views::MouseEvent& event) { + 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; + shelf_->DragExtension(); + } + } else { + // When freely dragging a window, you can really only trust the + // actual screen point. Coordinate conversions, just 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); + screen.set_x(screen.x() - origin.x() - initial_drag_location_.x()); + screen.set_y(screen.y() - origin.y() - initial_drag_location_.y()); + shelf_->DragHandleTo(screen); + } + return true; +} + +void ExtensionShelfHandle::OnMouseReleased(const views::MouseEvent& event, + bool canceled) { + if (dragging_) { + 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(loc, canceled); + } +} + +//////////////////////////////////////////////// + +ExtensionShelf::ExtensionShelf(Browser* browser) + : handle_(NULL), + handle_visible_(false), + current_toolstrip_(NULL), + ALLOW_THIS_IN_INITIALIZER_LIST(timer_factory_(this)), + drag_placeholder_view_(NULL), + model_(new ExtensionShelfModel(browser)) { + model_->AddObserver(this); + LoadFromModel(); +} + +ExtensionShelf::~ExtensionShelf() { + model_->RemoveObserver(this); +} + +BrowserBubble* ExtensionShelf::GetHandle() { + if (!handle_.get() && current_toolstrip_) { + ExtensionShelfHandle* handle_view = new ExtensionShelfHandle(this); + handle_view->SetExtensionView(current_toolstrip_->view()); + handle_.reset(new BrowserBubble(handle_view, GetWidget(), + gfx::Point(0, 0))); + handle_->set_delegate(this); + } + return handle_.get(); +} + +void ExtensionShelf::Paint(gfx::Canvas* canvas) { +#if 0 + // TODO(erikkay) re-enable this when Glen has the gradient values worked out. + SkPaint paint; + paint.setShader(skia::CreateGradientShader(0, + height(), + kTopGradientColor, + kBackgroundColor))->safeUnref(); + canvas->FillRectInt(0, 0, width(), height(), paint); +#else + canvas->FillRectInt(kBackgroundColor, 0, 0, width(), height()); +#endif + + canvas->FillRectInt(kBorderColor, 0, 0, width(), 1); + canvas->FillRectInt(kBorderColor, 0, height() - 1, width(), 1); + + int count = GetChildViewCount(); + for (int i = 0; i < count; ++i) { + int right = GetChildViewAt(i)->bounds().right() + kToolstripPadding; + int h = height() - 2; + canvas->FillRectInt(kBorderColor, right, 1, 1, h); + canvas->FillRectInt(kDividerHighlightColor, right + 1, 1, 1, h); + } + + SkRect background_rect = { + SkIntToScalar(0), + SkIntToScalar(1), + SkIntToScalar(1), + SkIntToScalar(height() - 2)}; + InitBackground(canvas, background_rect); +} + +gfx::Size ExtensionShelf::GetPreferredSize() { + if (model_->count()) + return gfx::Size(0, kShelfHeight); + return gfx::Size(0, 0); +} + +void ExtensionShelf::ChildPreferredSizeChanged(View* child) { + Layout(); +} + +void ExtensionShelf::Layout() { + if (!GetParent()) + return; + + int x = kLeftMargin; + int y = kTopMargin; + int content_height = height() - kTopMargin - kBottomMargin; + int max_x = width() - kRightMargin; + + int count = model_->count(); + for (int i = 0; i < count; ++i) { + x += kToolstripPadding; // left padding + ExtensionHost* toolstrip = model_->ToolstripAt(i); + ExtensionView* extension_view = toolstrip->view(); + gfx::Size pref = extension_view->GetPreferredSize(); + int next_x = x + pref.width() + kToolstripPadding; // right padding + extension_view->set_is_clipped(next_x >= max_x); + extension_view->SetBounds(x, y, pref.width(), content_height); + extension_view->Layout(); + x = next_x + kToolstripDividerWidth; + } + if (handle_.get()) + LayoutShelfHandle(); + SchedulePaint(); +} + +void ExtensionShelf::OnMouseEntered(const views::MouseEvent& event) { + ExtensionHost* toolstrip = ToolstripAtX(event.x()); + if (toolstrip) { + current_toolstrip_ = toolstrip; + ShowShelfHandle(); + } +} + +void ExtensionShelf::OnMouseExited(const views::MouseEvent& event) { + HideShelfHandle(kHideDelayMs); +} + +void ExtensionShelf::ToolstripInsertedAt(ExtensionHost* toolstrip, + int index) { + bool had_views = GetChildViewCount() > 0; + ExtensionView* view = toolstrip->view(); + if (!background_.empty()) + view->SetBackground(background_); + AddChildView(view); + view->SetContainer(this); + if (!had_views) + PreferredSizeChanged(); + Layout(); +} + +void ExtensionShelf::ToolstripRemovingAt(ExtensionHost* toolstrip, + int index) { + ExtensionView* view = toolstrip->view(); + RemoveChildView(view); + Layout(); +} + +void ExtensionShelf::ToolstripDraggingFrom(ExtensionHost* toolstrip, + int index) { +} + +void ExtensionShelf::ToolstripMoved(ExtensionHost* toolstrip, + int from_index, + int to_index) { + Layout(); +} + +void ExtensionShelf::ToolstripChangedAt(ExtensionHost* toolstrip, + int index) { +} + +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::OnExtensionMouseEvent(ExtensionView* view) { + // Ignore these events when dragging. + if (drag_placeholder_view_) + return; + ExtensionHost *toolstrip = ToolstripForView(view); + if (toolstrip != current_toolstrip_) + current_toolstrip_ = toolstrip; + if (current_toolstrip_) + ShowShelfHandle(); +} + +void ExtensionShelf::OnExtensionMouseLeave(ExtensionView* view) { + // Ignore these events when dragging. + if (drag_placeholder_view_) + return; + ExtensionHost *toolstrip = ToolstripForView(view); + if (toolstrip == current_toolstrip_) + HideShelfHandle(kHideDelayMs); +} + +void ExtensionShelf::BubbleBrowserWindowMoved(BrowserBubble* bubble) { + HideShelfHandle(0); +} + +void ExtensionShelf::BubbleBrowserWindowClosed(BrowserBubble* bubble) { + // We'll be going away shortly, so no need to do any other teardown here. + HideShelfHandle(0); +} + +void ExtensionShelf::DragExtension() { + // Construct a placeholder view to replace the view. + // TODO(erikkay) the placeholder should draw a dimmed version of the + // extension view + drag_placeholder_view_ = new View(); + drag_placeholder_view_->SetBounds(current_toolstrip_->view()->bounds()); + AddChildView(drag_placeholder_view_); + + // Now move the view into the handle's widget. + ExtensionShelfHandle* handle_view = + static_cast<ExtensionShelfHandle*>(GetHandle()->view()); + handle_view->AddChildView(current_toolstrip_->view()); + handle_view->SizeToPreferredSize(); + handle_->ResizeToView(); + handle_view->Layout(); + handle_->DetachFromBrowser(); + SchedulePaint(); +} + +void ExtensionShelf::DropExtension(const gfx::Point& pt, bool cancel) { + handle_->AttachToBrowser(); + + // Replace the placeholder view with the original. + AddChildView(current_toolstrip_->view()); + current_toolstrip_->view()->SetBounds(drag_placeholder_view_->bounds()); + RemoveChildView(drag_placeholder_view_); + delete drag_placeholder_view_; + drag_placeholder_view_ = NULL; + + ExtensionHost* toolstrip = ToolstripAtX(pt.x()); + if (toolstrip) { + int from = model_->IndexOfToolstrip(current_toolstrip_); + int to = model_->IndexOfToolstrip(toolstrip); + model_->MoveToolstripAt(from, to); + } + + ExtensionShelfHandle* handle_view = + static_cast<ExtensionShelfHandle*>(GetHandle()->view()); + handle_view->SizeToPreferredSize(); + handle_view->Layout(); + handle_->ResizeToView(); + LayoutShelfHandle(); + SchedulePaint(); +} + +void ExtensionShelf::DragHandleTo(const gfx::Point& pt) { + handle_->MoveTo(pt.x(), pt.y()); + // TODO(erikkay) as this gets dragged around, update the placeholder view + // on the shelf to show where it will get dropped to. +} + +void ExtensionShelf::InitBackground(gfx::Canvas* canvas, + const SkRect& subset) { + if (!background_.empty()) + return; + + const SkBitmap& background = canvas->getDevice()->accessBitmap(false); + + // Extract the correct subset of the toolstrip background into a bitmap. We + // must use a temporary here because extractSubset() returns a bitmap that + // references pixels in the original one and we want to actually make a copy + // that will have a long lifetime. + SkBitmap temp; + temp.setConfig(background.config(), + static_cast<int>(subset.width()), + static_cast<int>(subset.height())); + + SkRect mapped_subset = subset; + bool result = canvas->getTotalMatrix().mapRect(&mapped_subset); + DCHECK(result); + + SkIRect isubset; + mapped_subset.round(&isubset); + result = background.extractSubset(&temp, isubset); + if (!result) + return; + + temp.copyTo(&background_, temp.config()); + DCHECK(background_.readyToDraw()); + + // Tell all extension views about the new background + int count = model_->count(); + for (int i = 0; i < count; ++i) + model_->ToolstripAt(i)->view()->SetBackground(background_); +} + +ExtensionHost* ExtensionShelf::ToolstripAtX(int x) { + int count = model_->count(); + if (count == 0) + return NULL; + + if (x < 0) + return model_->ToolstripAt(0); + + for (int i = 0; i < count; ++i) { + ExtensionHost* toolstrip = model_->ToolstripAt(i); + ExtensionView* view = toolstrip->view(); + if (x > (view->x() + view->width() + kToolstripPadding)) + continue; + return toolstrip; + } + + return model_->ToolstripAt(count - 1); +} + +ExtensionHost* ExtensionShelf::ToolstripForView(ExtensionView* view) { + int count = model_->count(); + for (int i = 0; i < count; ++i) { + ExtensionHost* toolstrip = model_->ToolstripAt(i); + if (view == toolstrip->view()) + return toolstrip; + } + NOTREACHED(); + return NULL; +} + +void ExtensionShelf::ShowShelfHandle() { + if (drag_placeholder_view_) + return; + if (!timer_factory_.empty()) + timer_factory_.RevokeAll(); + if (handle_visible_) { + // The contents may have changed, even though the handle is still visible. + LayoutShelfHandle(); + return; + } + MessageLoop::current()->PostDelayedTask(FROM_HERE, + timer_factory_.NewRunnableMethod(&ExtensionShelf::DoShowShelfHandle), + 1000); +} + +void ExtensionShelf::DoShowShelfHandle() { + if (!handle_visible_) { + handle_visible_ = true; + LayoutShelfHandle(); + handle_->Show(); + } +} + +void ExtensionShelf::HideShelfHandle(int delay_ms) { + if (drag_placeholder_view_) + return; + if (!timer_factory_.empty()) + timer_factory_.RevokeAll(); + if (!handle_visible_) + return; + if (delay_ms) { + MessageLoop::current()->PostDelayedTask(FROM_HERE, + timer_factory_.NewRunnableMethod(&ExtensionShelf::DoHideShelfHandle), + delay_ms); + } else { + DoHideShelfHandle(); + } +} + +void ExtensionShelf::DoHideShelfHandle() { + if (handle_visible_) { + handle_visible_ = false; + handle_->Hide(); + handle_->DetachFromBrowser(); + handle_.reset(NULL); + current_toolstrip_ = NULL; + } +} + +void ExtensionShelf::LayoutShelfHandle() { + if (current_toolstrip_) { + GetHandle(); // ensure that the handle exists since we delete on hide + ExtensionShelfHandle* handle_view = + static_cast<ExtensionShelfHandle*>(GetHandle()->view()); + handle_view->SetExtensionView(current_toolstrip_->view()); + int width = std::max(current_toolstrip_->view()->width(), + handle_view->width()); + gfx::Point origin(-kToolstripPadding, + -(handle_view->height() + kToolstripPadding - 1)); + views::View::ConvertPointToWidget(current_toolstrip_->view(), &origin); + handle_view->SetBounds(0, 0, width, handle_view->height()); + handle_->SetBounds(origin.x(), origin.y(), + width, handle_view->height()); + } +} + +void ExtensionShelf::LoadFromModel() { + int count = model_->count(); + for (int i = 0; i < count; ++i) + ToolstripInsertedAt(model_->ToolstripAt(i), i); +} |