OLD | NEW |
| (Empty) |
1 // Copyright (c) 2010 The Chromium Authors. All rights reserved. | |
2 // Use of this source code is governed by a BSD-style license that can be | |
3 // found in the LICENSE file. | |
4 | |
5 #include "chrome/browser/views/extensions/extension_shelf.h" | |
6 | |
7 #include <algorithm> | |
8 | |
9 #include "app/resource_bundle.h" | |
10 #include "base/logging.h" | |
11 #include "base/message_loop.h" | |
12 #include "base/stl_util-inl.h" | |
13 #include "base/string_util.h" | |
14 #include "base/utf_string_conversions.h" | |
15 #include "chrome/browser/browser.h" | |
16 #include "chrome/browser/browser_theme_provider.h" | |
17 #include "chrome/browser/extensions/extension_host.h" | |
18 #include "chrome/browser/extensions/extension_process_manager.h" | |
19 #include "chrome/browser/extensions/extensions_service.h" | |
20 #include "chrome/browser/profile.h" | |
21 #include "chrome/browser/tab_contents/tab_contents.h" | |
22 #include "chrome/browser/views/extensions/extension_view.h" | |
23 #include "chrome/browser/view_ids.h" | |
24 #include "chrome/common/chrome_switches.h" | |
25 #include "chrome/common/extensions/extension.h" | |
26 #include "chrome/common/notification_service.h" | |
27 #include "chrome/common/pref_names.h" | |
28 #include "gfx/canvas_skia.h" | |
29 #include "views/controls/label.h" | |
30 #include "views/screen.h" | |
31 #include "views/widget/root_view.h" | |
32 | |
33 namespace { | |
34 | |
35 // This is the slight padding that is there around the edge of the browser. This | |
36 // has been determined empirically. | |
37 // TODO(sidchat): Compute this value from the root view of the extension shelf. | |
38 static const int kExtensionShelfPaddingOnLeft = 4; | |
39 | |
40 // Margins around the content. | |
41 static const int kTopMarginWhenExtensionsOnTop = 1; | |
42 static const int kTopMarginWhenExtensionsOnBottom = 2; | |
43 static const int kBottomMargin = 2; | |
44 static const int kLeftMargin = 0; | |
45 static const int kRightMargin = 0; | |
46 | |
47 // Padding on left and right side of an extension toolstrip. | |
48 static const int kToolstripPadding = 2; | |
49 | |
50 // Width of the toolstrip divider. | |
51 static const int kToolstripDividerWidth = 2; | |
52 | |
53 // Preferred height of the ExtensionShelf. | |
54 static const int kShelfHeight = 29; | |
55 | |
56 // Preferred height of the Extension shelf when only shown on the new tab page. | |
57 const int kNewtabShelfHeight = 58; | |
58 | |
59 // How inset the extension shelf is when displayed on the new tab page. This is | |
60 // in addition to the margins above. | |
61 static const int kNewtabHorizontalPadding = 8; | |
62 static const int kNewtabVerticalPadding = 12; | |
63 | |
64 // We need an extra margin to the left of all the toolstrips in detached mode, | |
65 // so that the first toolstrip doesn't look so squished against the rounded | |
66 // corners of the extension shelf. | |
67 static const int kNewtabExtraHorMargin = 2; | |
68 static const int kNewtabExtraVerMargin = 2; | |
69 | |
70 // Padding for the title inside the handle. | |
71 static const int kHandlePadding = 4; | |
72 | |
73 // Inset for the extension view when displayed either dragging or expanded. | |
74 static const int kWindowInset = 1; | |
75 | |
76 // Delays for showing and hiding the shelf handle. | |
77 static const int kShowDelayMs = 500; | |
78 static const int kHideDelayMs = 300; | |
79 | |
80 } // namespace | |
81 | |
82 | |
83 // A view that holds the place for a toolstrip in the shelf while the toolstrip | |
84 // is being dragged or moved. | |
85 // TODO(erikkay) this should draw a dimmed out version of the toolstrip. | |
86 class ExtensionShelf::PlaceholderView : public views::View { | |
87 public: | |
88 PlaceholderView() {} | |
89 | |
90 void SetWidth(int width) { | |
91 SetBounds(x(), y(), width, height()); | |
92 PreferredSizeChanged(); | |
93 } | |
94 | |
95 // ExtensionShelf resizes its views to their preferred size at layout, | |
96 // so just always prefer the current size. | |
97 gfx::Size GetPreferredSize() { return size(); } | |
98 | |
99 private: | |
100 DISALLOW_COPY_AND_ASSIGN(PlaceholderView); | |
101 }; | |
102 | |
103 // A wrapper class for the ExtensionHost displayed as a toolstrip. | |
104 // The class itself also acts as the View for the handle of the toolstrip | |
105 // it represents. | |
106 class ExtensionShelf::Toolstrip : public views::View, | |
107 public BrowserBubble::Delegate, | |
108 public AnimationDelegate { | |
109 public: | |
110 Toolstrip(ExtensionShelf* shelf, ExtensionHost* host, | |
111 const Extension::ToolstripInfo& info); | |
112 virtual ~Toolstrip(); | |
113 | |
114 // Convenience to calculate just the size of the handle. | |
115 gfx::Size GetHandlePreferredSize(); | |
116 | |
117 // View methods: | |
118 virtual void Paint(gfx::Canvas* canvas); | |
119 virtual gfx::Size GetPreferredSize(); | |
120 virtual void Layout(); | |
121 virtual void OnMouseEntered(const views::MouseEvent& event); | |
122 virtual void OnMouseExited(const views::MouseEvent& event); | |
123 virtual bool OnMousePressed(const views::MouseEvent& event); | |
124 virtual bool OnMouseDragged(const views::MouseEvent& event); | |
125 virtual void OnMouseReleased(const views::MouseEvent& event, bool canceled); | |
126 virtual bool IsFocusable() const { return true; } | |
127 virtual void ChildPreferredSizeChanged(View* child); | |
128 | |
129 // Adjust the size and position of the window/handle. | |
130 void LayoutWindow(); | |
131 | |
132 // Is the toolstrip window visible (not necessarily the handle). | |
133 bool window_visible() { return (window_.get() && window_->visible()); } | |
134 | |
135 // Is the handle for this toolstrip currently visible. | |
136 bool handle_visible() { return (window_visible() && handle_visible_); } | |
137 | |
138 // Is the toolstrip opened in expanded mode. | |
139 bool expanded() { return expanded_; } | |
140 | |
141 // Is the toolstrip being dragged. | |
142 bool dragging() { return dragging_; } | |
143 | |
144 // Accessors for the host and its view. | |
145 ExtensionHost* host() const { return host_; } | |
146 ExtensionView* view() const { return host_->view(); } | |
147 | |
148 // The view that's currently displayed in the shelf. This could be | |
149 // either the ExtensionView or |placeholder_view_| depending on the | |
150 // current state. | |
151 View* GetShelfView() const { | |
152 if (placeholder_view_) | |
153 return placeholder_view_; | |
154 return view(); | |
155 } | |
156 | |
157 // Detaching the toolstrip from the shelf means that the ExtensionView is | |
158 // displayed inside of the BrowserBubble rather than the shelf. This may | |
159 // still visually appear to be part of the shelf (expanded) or completely | |
160 // separate (dragging). | |
161 // If |browser| is true, it also will detach/attach to the browser window. | |
162 void DetachFromShelf(bool browser); | |
163 void AttachToShelf(bool browser); | |
164 | |
165 // Show / Hide the shelf handle. | |
166 void ShowShelfHandle(); | |
167 void DoShowShelfHandle(); | |
168 void HideShelfHandle(int delay_ms); | |
169 void DoHideShelfHandle(); | |
170 void StopHandleTimer(); | |
171 void HideWindow(); | |
172 void ShowWindow(); | |
173 | |
174 // Expand / Collapse | |
175 void Expand(int height, const GURL& url); | |
176 void Collapse(const GURL& url); | |
177 | |
178 // BrowserBubble::Delegate | |
179 virtual void BubbleBrowserWindowMoved(BrowserBubble* bubble); | |
180 virtual void BubbleBrowserWindowClosing(BrowserBubble* bubble); | |
181 | |
182 // AnimationDelegate | |
183 virtual void AnimationProgressed(const Animation* animation); | |
184 virtual void AnimationEnded(const Animation* animation); | |
185 | |
186 private: | |
187 // The actual renderer that this toolstrip contains. | |
188 ExtensionHost* host_; | |
189 | |
190 // Manifest definition of this toolstrip. | |
191 Extension::ToolstripInfo info_; | |
192 | |
193 // A window that can be logically attached to the browser window. This is | |
194 // used for two purposes: a handle that sits above the toolstrip when it's | |
195 // collapsed and not dragging, and as a container for the toolstrip when it's | |
196 // dragging or expanded. | |
197 scoped_ptr<BrowserBubble> window_; | |
198 | |
199 // Used for drawing the name of the extension in the handle. | |
200 scoped_ptr<views::Label> title_; | |
201 | |
202 // Pointer back to the containing shelf. | |
203 ExtensionShelf* shelf_; | |
204 | |
205 // When dragging, a placeholder view is put into the shelf to hold space | |
206 // for the ExtensionView. This view is parent owned when it's in the view | |
207 // hierarchy, so there's no ownership issues here. | |
208 PlaceholderView* placeholder_view_; | |
209 | |
210 // Current state of the toolstrip, currently can't both be expanded and | |
211 // dragging. | |
212 // TODO(erikkay) Support dragging while expanded. | |
213 bool dragging_; | |
214 bool expanded_; | |
215 bool handle_visible_; | |
216 | |
217 // The target expanded height of the toolstrip (used for animation). | |
218 int expanded_height_; | |
219 | |
220 // If dragging, where did the drag start from. | |
221 gfx::Point initial_drag_location_; | |
222 | |
223 // We have to remember the initial drag point in screen coordinates, because | |
224 // later, when the toolstrip is being dragged around, there is no good way of | |
225 // computing the screen coordinates given the initial drag view coordinates. | |
226 gfx::Point initial_drag_screen_point_; | |
227 | |
228 // Timers for tracking mouse hovering. | |
229 ScopedRunnableMethodFactory<ExtensionShelf::Toolstrip> timer_factory_; | |
230 | |
231 // Animate opening and closing the mole. | |
232 scoped_ptr<SlideAnimation> mole_animation_; | |
233 | |
234 DISALLOW_COPY_AND_ASSIGN(Toolstrip); | |
235 }; | |
236 | |
237 ExtensionShelf::Toolstrip::Toolstrip(ExtensionShelf* shelf, | |
238 ExtensionHost* host, | |
239 const Extension::ToolstripInfo& info) | |
240 : host_(host), | |
241 info_(info), | |
242 shelf_(shelf), | |
243 placeholder_view_(NULL), | |
244 dragging_(false), | |
245 expanded_(false), | |
246 handle_visible_(false), | |
247 expanded_height_(0), | |
248 ALLOW_THIS_IN_INITIALIZER_LIST(timer_factory_(this)) { | |
249 DCHECK(host->view()); | |
250 // We're owned by shelf_, not the bubble that we get inserted in and out of. | |
251 set_parent_owned(false); | |
252 | |
253 mole_animation_.reset(new SlideAnimation(this)); | |
254 | |
255 std::wstring name = UTF8ToWide(host_->extension()->name()); | |
256 // |title_| isn't actually put in the view hierarchy. We just use it | |
257 // to draw in place. The reason for this is so that we can properly handle | |
258 // the various mouse events necessary for hovering and dragging. | |
259 ResourceBundle& rb = ResourceBundle::GetSharedInstance(); | |
260 title_.reset(new views::Label(name, rb.GetFont(ResourceBundle::BaseFont))); | |
261 title_->SetBounds(kHandlePadding, kHandlePadding, 100, 100); | |
262 title_->SizeToPreferredSize(); | |
263 | |
264 SizeToPreferredSize(); | |
265 } | |
266 | |
267 ExtensionShelf::Toolstrip::~Toolstrip() { | |
268 if (window_.get() && window_->attached()) | |
269 window_->DetachFromBrowser(); | |
270 } | |
271 | |
272 void ExtensionShelf::Toolstrip::Paint(gfx::Canvas* canvas) { | |
273 // Paint the background. | |
274 SkColor theme_toolbar_color = | |
275 shelf_->GetThemeProvider()->GetColor(BrowserThemeProvider::COLOR_TOOLBAR); | |
276 canvas->FillRectInt(theme_toolbar_color, 0, 0, width(), height()); | |
277 | |
278 // Paints the handle for the toolstrip (only called on mouse-hover). | |
279 SkColor border_color = ResourceBundle::toolbar_separator_color; | |
280 canvas->FillRectInt(border_color, 0, 0, width(), 1); | |
281 canvas->FillRectInt(border_color, 0, 0, 1, height() - 1); | |
282 canvas->FillRectInt(border_color, width() - 1, 0, 1, height() - 1); | |
283 int ext_width = view()->width() + kToolstripPadding + | |
284 kToolstripDividerWidth; | |
285 if (ext_width < width()) { | |
286 canvas->FillRectInt(border_color, ext_width, height() - 1, | |
287 width() - ext_width, 1); | |
288 } | |
289 | |
290 if (handle_visible_) { | |
291 // Draw the title using a Label as a stamp. | |
292 // See constructor for comment about this. | |
293 title_->ProcessPaint(canvas); | |
294 | |
295 if (dragging_) { | |
296 // When we're dragging, draw the bottom border. | |
297 canvas->FillRectInt(border_color, 0, height() - 1, width(), 1); | |
298 } | |
299 } | |
300 } | |
301 | |
302 gfx::Size ExtensionShelf::Toolstrip::GetHandlePreferredSize() { | |
303 gfx::Size sz; | |
304 if (handle_visible_) { | |
305 sz = title_->GetPreferredSize(); | |
306 sz.set_width(std::max(view()->width(), sz.width())); | |
307 sz.Enlarge(2 + kHandlePadding * 2, kHandlePadding * 2); | |
308 } | |
309 return sz; | |
310 } | |
311 | |
312 gfx::Size ExtensionShelf::Toolstrip::GetPreferredSize() { | |
313 gfx::Size sz; | |
314 if (handle_visible_) | |
315 sz = GetHandlePreferredSize(); | |
316 if (view()->GetParent() == this) { | |
317 gfx::Size extension_size = view()->GetPreferredSize(); | |
318 sz.Enlarge(0, extension_size.height()); | |
319 sz.set_width(extension_size.width()); | |
320 } | |
321 | |
322 // The view is inset slightly when displayed in the window. | |
323 sz.Enlarge(kWindowInset * 2, kWindowInset * 2); | |
324 return sz; | |
325 } | |
326 | |
327 void ExtensionShelf::Toolstrip::Layout() { | |
328 if (view()->GetParent() == this) { | |
329 int view_y = kWindowInset; | |
330 if (handle_visible_) | |
331 view_y += GetHandlePreferredSize().height(); | |
332 view()->SetBounds(kWindowInset, view_y, view()->width(), view()->height()); | |
333 } | |
334 } | |
335 | |
336 void ExtensionShelf::Toolstrip::OnMouseEntered(const views::MouseEvent& event) { | |
337 if (dragging_) | |
338 return; | |
339 ShowShelfHandle(); | |
340 } | |
341 | |
342 void ExtensionShelf::Toolstrip::OnMouseExited(const views::MouseEvent& event) { | |
343 if (dragging_) | |
344 return; | |
345 HideShelfHandle(kHideDelayMs); | |
346 } | |
347 | |
348 bool ExtensionShelf::Toolstrip::OnMousePressed(const views::MouseEvent& event) { | |
349 initial_drag_location_ = event.location(); | |
350 initial_drag_screen_point_ = views::Screen::GetCursorScreenPoint(); | |
351 return true; | |
352 } | |
353 | |
354 bool ExtensionShelf::Toolstrip::OnMouseDragged(const views::MouseEvent& event) { | |
355 if (expanded_) { | |
356 // Do nothing for now. | |
357 } else if (!dragging_) { | |
358 int y_delta = abs(initial_drag_location_.y() - event.location().y()); | |
359 int x_delta = abs(initial_drag_location_.x() - event.location().x()); | |
360 if (y_delta > GetVerticalDragThreshold() || | |
361 x_delta > GetHorizontalDragThreshold()) { | |
362 dragging_ = true; | |
363 StopHandleTimer(); | |
364 DetachFromShelf(true); | |
365 } | |
366 } else { | |
367 // When freely dragging a window, you can really only trust the | |
368 // actual screen point. Local coordinate conversions don't work. | |
369 gfx::Point screen = views::Screen::GetCursorScreenPoint(); | |
370 | |
371 // However, the handle is actually a child of the browser window | |
372 // so we need to convert it back to local coordinates. | |
373 gfx::Point origin(0, 0); | |
374 views::View::ConvertPointToScreen(shelf_->GetRootView(), &origin); | |
375 int screen_x = screen.x() - initial_drag_location_.x() - origin.x(); | |
376 | |
377 // Restrict the horizontal and vertical motions of the toolstrip so that it | |
378 // cannot be dragged out of the extension shelf. If the extension shelf is | |
379 // on the top along with the bookmark bar, the toolstrip cannot be dragged | |
380 // into the space allocated for bookmarks. The toolstrip cannot be dragged | |
381 // out of the browser window. | |
382 if (screen_x < kExtensionShelfPaddingOnLeft) { | |
383 screen_x = kExtensionShelfPaddingOnLeft; | |
384 } else if (screen_x > shelf_->width() - width() + | |
385 kExtensionShelfPaddingOnLeft) { | |
386 screen_x = shelf_->width() - width() + kExtensionShelfPaddingOnLeft; | |
387 } | |
388 screen.set_x(screen_x); | |
389 screen.set_y(initial_drag_screen_point_.y() - origin.y() - | |
390 initial_drag_location_.y()); | |
391 | |
392 // TODO(erikkay) as this gets dragged around, update the placeholder view | |
393 // on the shelf to show where it will get dropped to. | |
394 window_->MoveTo(screen.x(), screen.y()); | |
395 } | |
396 return true; | |
397 } | |
398 | |
399 void ExtensionShelf::Toolstrip::OnMouseReleased(const views::MouseEvent& event, | |
400 bool canceled) { | |
401 StopHandleTimer(); | |
402 if (dragging_) { | |
403 // Drop the toolstrip roughly where it is now. | |
404 views::View::OnMouseReleased(event, canceled); | |
405 dragging_ = false; | |
406 // |this| and |shelf_| are in different view hierarchies, so we need to | |
407 // convert to screen coordinates and back again to map locations. | |
408 gfx::Point loc = event.location(); | |
409 View::ConvertPointToScreen(this, &loc); | |
410 View::ConvertPointToView(NULL, shelf_, &loc); | |
411 shelf_->DropExtension(this, loc, canceled); | |
412 AttachToShelf(true); | |
413 } else if (!canceled) { | |
414 // Toggle mole to either expanded or collapsed. | |
415 // TODO(erikkay) If there's no valid URL in the manifest, should we | |
416 // post an event to the toolstrip in this case? | |
417 if (expanded_) { | |
418 if (info_.toolstrip.is_valid()) | |
419 shelf_->CollapseToolstrip(host_, info_.toolstrip); | |
420 } else { | |
421 if (info_.mole.is_valid()) | |
422 shelf_->ExpandToolstrip(host_, info_.mole, info_.mole_height); | |
423 } | |
424 } | |
425 } | |
426 | |
427 void ExtensionShelf::Toolstrip::LayoutWindow() { | |
428 if (!window_visible() && !handle_visible_ && !expanded_) | |
429 return; | |
430 | |
431 if (!window_.get()) { | |
432 window_.reset(new BrowserBubble(this, shelf_->GetWidget(), | |
433 gfx::Point(0, 0), | |
434 false)); // Do not add a drop-shadow. | |
435 window_->set_delegate(this); | |
436 } | |
437 | |
438 gfx::Size window_size = GetPreferredSize(); | |
439 if (mole_animation_->is_animating()) { | |
440 // We only want to animate the body of the mole window. When we're | |
441 // expanding, this is everything except for the handle. When we're | |
442 // collapsing, this is everything except for the handle and the toolstrip. | |
443 // We subtract this amount from the target height, figure out the step in | |
444 // the animation from the rest, and then add it back in. | |
445 int window_offset = shelf_->height(); | |
446 if (!mole_animation_->IsShowing()) | |
447 window_offset += GetPreferredSize().height(); | |
448 else | |
449 window_offset += GetHandlePreferredSize().height(); | |
450 int h = expanded_height_ - window_offset; | |
451 window_size.set_height(window_offset + | |
452 static_cast<int>(h * mole_animation_->GetCurrentValue())); | |
453 } else if (!expanded_ && !dragging_) { | |
454 window_size.set_height(GetHandlePreferredSize().height()); | |
455 } | |
456 | |
457 // Now figure out where to place the window on the screen. Since it's a top- | |
458 // level widget, we need to do some coordinate conversion to get this right. | |
459 gfx::Point origin(-kToolstripPadding, 0); | |
460 if (expanded_ || mole_animation_->is_animating()) { | |
461 origin.set_y(GetShelfView()->height() - window_size.height()); | |
462 views::View::ConvertPointToView(GetShelfView(), shelf_->GetRootView(), | |
463 &origin); | |
464 } else { | |
465 origin.set_y(-(window_size.height() + kToolstripPadding - 1)); | |
466 views::View::ConvertPointToWidget(view(), &origin); | |
467 } | |
468 SetBounds(0, 0, window_size.width(), window_size.height()); | |
469 window_->SetBounds(origin.x(), origin.y(), | |
470 window_size.width(), window_size.height()); | |
471 } | |
472 | |
473 void ExtensionShelf::Toolstrip::ChildPreferredSizeChanged(View* child) { | |
474 if (child == view()) { | |
475 child->SizeToPreferredSize(); | |
476 Layout(); | |
477 if (window_visible()) | |
478 LayoutWindow(); | |
479 if (expanded_) { | |
480 placeholder_view_->SetWidth(child->width()); | |
481 shelf_->Layout(); | |
482 } | |
483 } | |
484 } | |
485 | |
486 void ExtensionShelf::Toolstrip::BubbleBrowserWindowMoved( | |
487 BrowserBubble* bubble) { | |
488 if (!expanded_) | |
489 HideWindow(); | |
490 } | |
491 | |
492 void ExtensionShelf::Toolstrip::BubbleBrowserWindowClosing( | |
493 BrowserBubble* bubble) { | |
494 HideWindow(); | |
495 } | |
496 | |
497 void ExtensionShelf::Toolstrip::AnimationProgressed( | |
498 const Animation* animation) { | |
499 LayoutWindow(); | |
500 } | |
501 | |
502 void ExtensionShelf::Toolstrip::AnimationEnded(const Animation* animation) { | |
503 if (window_visible()) | |
504 LayoutWindow(); | |
505 if (!expanded_) { | |
506 AttachToShelf(false); | |
507 HideShelfHandle(kHideDelayMs); | |
508 } | |
509 } | |
510 | |
511 void ExtensionShelf::Toolstrip::DetachFromShelf(bool browserDetach) { | |
512 DCHECK(window_.get()); | |
513 DCHECK(!placeholder_view_); | |
514 if (browserDetach && window_->attached()) | |
515 window_->DetachFromBrowser(); | |
516 | |
517 // Construct a placeholder view to replace the view. | |
518 placeholder_view_ = new PlaceholderView(); | |
519 placeholder_view_->SetBounds(view()->bounds()); | |
520 shelf_->AddChildView(placeholder_view_); | |
521 | |
522 AddChildView(view()); | |
523 SizeToPreferredSize(); | |
524 window_->ResizeToView(); | |
525 Layout(); | |
526 } | |
527 | |
528 void ExtensionShelf::Toolstrip::AttachToShelf(bool browserAttach) { | |
529 DCHECK(window_.get()); | |
530 DCHECK(placeholder_view_); | |
531 if (browserAttach && !window_->attached()) | |
532 window_->AttachToBrowser(); | |
533 | |
534 // Move the view back into the shelf and remove the old placeholder. | |
535 shelf_->AddChildView(view()); | |
536 | |
537 // The size of the view may have changed, so just set the position. | |
538 view()->SetX(placeholder_view_->x()); | |
539 view()->SetY(placeholder_view_->y()); | |
540 | |
541 // Remove the old placeholder. | |
542 shelf_->RemoveChildView(placeholder_view_); | |
543 delete placeholder_view_; | |
544 placeholder_view_ = NULL; | |
545 | |
546 SizeToPreferredSize(); | |
547 Layout(); | |
548 shelf_->Layout(); | |
549 } | |
550 | |
551 void ExtensionShelf::Toolstrip::DoShowShelfHandle() { | |
552 if (!handle_visible()) { | |
553 handle_visible_ = true; | |
554 | |
555 // Make sure the text color for the title matches the theme colors. | |
556 title_->SetColor( | |
557 shelf_->GetThemeProvider()->GetColor( | |
558 BrowserThemeProvider::COLOR_BOOKMARK_TEXT)); | |
559 | |
560 ShowWindow(); | |
561 } | |
562 } | |
563 | |
564 void ExtensionShelf::Toolstrip::HideWindow() { | |
565 if (!window_visible()) | |
566 return; | |
567 handle_visible_ = false; | |
568 window_->Hide(); | |
569 if (expanded_) { | |
570 if (info_.toolstrip.is_valid()) | |
571 shelf_->CollapseToolstrip(host_, info_.toolstrip); | |
572 else | |
573 shelf_->CollapseToolstrip(host_, GURL()); | |
574 } | |
575 if (window_->attached()) | |
576 window_->DetachFromBrowser(); | |
577 window_.reset(NULL); | |
578 shelf_->Layout(); | |
579 } | |
580 | |
581 void ExtensionShelf::Toolstrip::ShowWindow() { | |
582 DCHECK(handle_visible_ || expanded_); | |
583 | |
584 LayoutWindow(); | |
585 if (!window_visible()) | |
586 window_->Show(false); // |false| means show, but don't activate. | |
587 } | |
588 | |
589 void ExtensionShelf::Toolstrip::DoHideShelfHandle() { | |
590 if (!handle_visible()) | |
591 return; | |
592 handle_visible_ = false; | |
593 if (expanded_) { | |
594 LayoutWindow(); | |
595 Layout(); | |
596 } else { | |
597 HideWindow(); | |
598 } | |
599 } | |
600 | |
601 void ExtensionShelf::Toolstrip::StopHandleTimer() { | |
602 if (!timer_factory_.empty()) | |
603 timer_factory_.RevokeAll(); | |
604 } | |
605 | |
606 void ExtensionShelf::Toolstrip::Expand(int height, const GURL& url) { | |
607 DCHECK(!expanded_); | |
608 | |
609 expanded_ = true; | |
610 ShowWindow(); | |
611 | |
612 bool navigate = (!url.is_empty() && url != host_->GetURL()); | |
613 if (navigate) | |
614 host_->NavigateToURL(url); | |
615 | |
616 StopHandleTimer(); | |
617 DetachFromShelf(false); | |
618 | |
619 mole_animation_->Show(); | |
620 | |
621 gfx::Size extension_size = view()->GetPreferredSize(); | |
622 extension_size.set_height(height); | |
623 view()->SetPreferredSize(extension_size); | |
624 expanded_height_ = GetPreferredSize().height(); | |
625 | |
626 // This is to prevent flickering as the page loads and lays out. | |
627 // Once the navigation is finished, ExtensionView will wind up setting | |
628 // visibility to true. | |
629 if (navigate) | |
630 view()->SetVisible(false); | |
631 } | |
632 | |
633 void ExtensionShelf::Toolstrip::Collapse(const GURL& url) { | |
634 DCHECK(expanded_); | |
635 expanded_ = false; | |
636 | |
637 if (window_visible()) | |
638 mole_animation_->Hide(); | |
639 | |
640 gfx::Size extension_size = view()->GetPreferredSize(); | |
641 extension_size.set_height( | |
642 kShelfHeight - (shelf_->top_margin() + kBottomMargin)); | |
643 view()->SetPreferredSize(extension_size); | |
644 | |
645 if (!url.is_empty() && url != host_->GetURL()) { | |
646 host_->NavigateToURL(url); | |
647 | |
648 // This is to prevent flickering as the page loads and lays out. | |
649 // Once the navigation is finished, ExtensionView will wind up setting | |
650 // visibility to true. | |
651 view()->SetVisible(false); | |
652 } | |
653 | |
654 if (!window_visible()) | |
655 AnimationEnded(NULL); | |
656 } | |
657 | |
658 void ExtensionShelf::Toolstrip::ShowShelfHandle() { | |
659 StopHandleTimer(); | |
660 if (handle_visible()) | |
661 return; | |
662 MessageLoop::current()->PostDelayedTask(FROM_HERE, | |
663 timer_factory_.NewRunnableMethod( | |
664 &ExtensionShelf::Toolstrip::DoShowShelfHandle), | |
665 kShowDelayMs); | |
666 } | |
667 | |
668 void ExtensionShelf::Toolstrip::HideShelfHandle(int delay_ms) { | |
669 StopHandleTimer(); | |
670 if (!handle_visible() || dragging_ || mole_animation_->is_animating()) | |
671 return; | |
672 if (delay_ms) { | |
673 MessageLoop::current()->PostDelayedTask(FROM_HERE, | |
674 timer_factory_.NewRunnableMethod( | |
675 &ExtensionShelf::Toolstrip::DoHideShelfHandle), | |
676 delay_ms); | |
677 } else { | |
678 DoHideShelfHandle(); | |
679 } | |
680 } | |
681 | |
682 //////////////////////////////////////////////////////////////////////////////// | |
683 | |
684 ExtensionShelf::ExtensionShelf(Browser* browser) | |
685 : background_needs_repaint_(true), | |
686 browser_(browser), | |
687 model_(browser->extension_shelf_model()), | |
688 fullscreen_(false) { | |
689 SetID(VIEW_ID_DEV_EXTENSION_SHELF); | |
690 | |
691 top_margin_ = kTopMarginWhenExtensionsOnBottom; | |
692 | |
693 model_->AddObserver(this); | |
694 LoadFromModel(); | |
695 EnableCanvasFlippingForRTLUI(true); | |
696 registrar_.Add(this, | |
697 NotificationType::EXTENSION_SHELF_VISIBILITY_PREF_CHANGED, | |
698 NotificationService::AllSources()); | |
699 | |
700 size_animation_.reset(new SlideAnimation(this)); | |
701 if (IsAlwaysShown()) | |
702 size_animation_->Reset(1); | |
703 else | |
704 size_animation_->Reset(0); | |
705 } | |
706 | |
707 ExtensionShelf::~ExtensionShelf() { | |
708 if (model_) { | |
709 int count = model_->count(); | |
710 for (int i = 0; i < count; ++i) { | |
711 delete ToolstripAtIndex(i); | |
712 model_->SetToolstripDataAt(i, NULL); | |
713 } | |
714 model_->RemoveObserver(this); | |
715 } | |
716 } | |
717 | |
718 void ExtensionShelf::PaintChildren(gfx::Canvas* canvas) { | |
719 InitBackground(canvas); | |
720 | |
721 int max_x = width(); | |
722 if (IsDetached()) | |
723 max_x -= 2 * kNewtabHorizontalPadding; | |
724 | |
725 // Draw vertical dividers between Toolstrip items in the Extension shelf. | |
726 int count = GetChildViewCount(); | |
727 for (int i = 0; i < count; ++i) { | |
728 int right = GetChildViewAt(i)->bounds().right() + kToolstripPadding; | |
729 if (right >= max_x) | |
730 break; | |
731 int vertical_padding = IsDetached() ? (height() - kShelfHeight) / 2 : 1; | |
732 | |
733 DetachableToolbarView::PaintVerticalDivider( | |
734 canvas, right, height(), vertical_padding, | |
735 DetachableToolbarView::kEdgeDividerColor, | |
736 DetachableToolbarView::kMiddleDividerColor, | |
737 GetThemeProvider()->GetColor(BrowserThemeProvider::COLOR_TOOLBAR)); | |
738 } | |
739 } | |
740 | |
741 // static | |
742 void ExtensionShelf::ToggleWhenExtensionShelfVisible(Profile* profile) { | |
743 PrefService* prefs = profile->GetPrefs(); | |
744 const bool always_show = !prefs->GetBoolean(prefs::kShowExtensionShelf); | |
745 | |
746 // The user changed when the Extension Shelf is shown, update the | |
747 // preferences. | |
748 prefs->SetBoolean(prefs::kShowExtensionShelf, always_show); | |
749 prefs->ScheduleSavePersistentPrefs(); | |
750 | |
751 // And notify the notification service. | |
752 Source<Profile> source(profile); | |
753 NotificationService::current()->Notify( | |
754 NotificationType::EXTENSION_SHELF_VISIBILITY_PREF_CHANGED, | |
755 source, | |
756 NotificationService::NoDetails()); | |
757 } | |
758 | |
759 gfx::Size ExtensionShelf::GetPreferredSize() { | |
760 return LayoutItems(true); | |
761 } | |
762 | |
763 void ExtensionShelf::ChildPreferredSizeChanged(View* child) { | |
764 PreferredSizeChanged(); | |
765 } | |
766 | |
767 void ExtensionShelf::Layout() { | |
768 LayoutItems(false); | |
769 } | |
770 | |
771 void ExtensionShelf::OnMouseEntered(const views::MouseEvent& event) { | |
772 } | |
773 | |
774 void ExtensionShelf::OnMouseExited(const views::MouseEvent& event) { | |
775 } | |
776 | |
777 bool ExtensionShelf::GetAccessibleRole(AccessibilityTypes::Role* role) { | |
778 DCHECK(role); | |
779 | |
780 *role = AccessibilityTypes::ROLE_TOOLBAR; | |
781 return true; | |
782 } | |
783 | |
784 void ExtensionShelf::OnThemeChanged() { | |
785 // Refresh the CSS to update toolstrip text colors from theme. | |
786 int count = model_->count(); | |
787 for (int i = 0; i < count; ++i) | |
788 ToolstripAtIndex(i)->view()->host()->InsertThemedToolstripCSS(); | |
789 | |
790 Layout(); | |
791 } | |
792 | |
793 void ExtensionShelf::ToolstripInsertedAt(ExtensionHost* host, | |
794 int index) { | |
795 model_->SetToolstripDataAt(index, | |
796 new Toolstrip(this, host, model_->ToolstripAt(index).info)); | |
797 | |
798 bool had_views = GetChildViewCount() > 0; | |
799 ExtensionView* view = host->view(); | |
800 AddChildView(view); | |
801 view->SetContainer(this); | |
802 if (!had_views) | |
803 PreferredSizeChanged(); | |
804 Layout(); | |
805 } | |
806 | |
807 void ExtensionShelf::ToolstripRemovingAt(ExtensionHost* host, int index) { | |
808 // Delete the Toolstrip view and remove it from the model. | |
809 Toolstrip* toolstrip = ToolstripAtIndex(index); | |
810 View* view = toolstrip->GetShelfView(); | |
811 RemoveChildView(view); | |
812 delete toolstrip; | |
813 model_->SetToolstripDataAt(index, NULL); | |
814 | |
815 // Technically, the toolstrip is still in the model at this point, but | |
816 // the Layout code handles this case. | |
817 Layout(); | |
818 } | |
819 | |
820 void ExtensionShelf::ToolstripDraggingFrom(ExtensionHost* host, int index) { | |
821 } | |
822 | |
823 void ExtensionShelf::ToolstripMoved(ExtensionHost* host, int from_index, | |
824 int to_index) { | |
825 Layout(); | |
826 } | |
827 | |
828 void ExtensionShelf::ToolstripChanged(ExtensionShelfModel::iterator toolstrip) { | |
829 Toolstrip* t = static_cast<Toolstrip*>(toolstrip->data); | |
830 if (toolstrip->height > 0) { | |
831 if (!t->expanded()) { | |
832 t->Expand(toolstrip->height, toolstrip->url); | |
833 } | |
834 } else if (t->expanded()) { | |
835 t->Collapse(toolstrip->url); | |
836 } | |
837 } | |
838 | |
839 void ExtensionShelf::ExtensionShelfEmpty() { | |
840 PreferredSizeChanged(); | |
841 } | |
842 | |
843 void ExtensionShelf::ShelfModelReloaded() { | |
844 // None of the child views are parent owned, so nothing is being leaked here. | |
845 RemoveAllChildViews(false); | |
846 LoadFromModel(); | |
847 } | |
848 | |
849 void ExtensionShelf::ShelfModelDeleting() { | |
850 int count = model_->count(); | |
851 for (int i = 0; i < count; ++i) { | |
852 delete ToolstripAtIndex(i); | |
853 model_->SetToolstripDataAt(i, NULL); | |
854 } | |
855 model_->RemoveObserver(this); | |
856 model_ = NULL; | |
857 } | |
858 | |
859 void ExtensionShelf::AnimationProgressed(const Animation* animation) { | |
860 if (browser_) | |
861 browser_->ExtensionShelfSizeChanged(); | |
862 } | |
863 | |
864 void ExtensionShelf::AnimationEnded(const Animation* animation) { | |
865 if (browser_) | |
866 browser_->ExtensionShelfSizeChanged(); | |
867 | |
868 Layout(); | |
869 } | |
870 | |
871 void ExtensionShelf::Observe(NotificationType type, | |
872 const NotificationSource& source, | |
873 const NotificationDetails& details) { | |
874 switch (type.value) { | |
875 case NotificationType::EXTENSION_SHELF_VISIBILITY_PREF_CHANGED: { | |
876 if (fullscreen_) | |
877 break; | |
878 if (IsAlwaysShown()) | |
879 size_animation_->Show(); | |
880 else | |
881 size_animation_->Hide(); | |
882 break; | |
883 } | |
884 default: | |
885 NOTREACHED(); | |
886 break; | |
887 } | |
888 } | |
889 | |
890 void ExtensionShelf::OnExtensionMouseMove(ExtensionView* view) { | |
891 Toolstrip *toolstrip = ToolstripForView(view); | |
892 if (toolstrip) | |
893 toolstrip->ShowShelfHandle(); | |
894 } | |
895 | |
896 void ExtensionShelf::OnExtensionMouseLeave(ExtensionView* view) { | |
897 Toolstrip *toolstrip = ToolstripForView(view); | |
898 if (toolstrip) | |
899 toolstrip->HideShelfHandle(kHideDelayMs); | |
900 } | |
901 | |
902 void ExtensionShelf::DropExtension(Toolstrip* toolstrip, const gfx::Point& pt, | |
903 bool cancel) { | |
904 Toolstrip* dest_toolstrip = ToolstripAtX(pt.x()); | |
905 if (!dest_toolstrip) { | |
906 if (pt.x() > 0) | |
907 dest_toolstrip = ToolstripAtIndex(model_->count() - 1); | |
908 else | |
909 dest_toolstrip = ToolstripAtIndex(0); | |
910 } | |
911 if (toolstrip == dest_toolstrip) | |
912 return; | |
913 int from = model_->IndexOfHost(toolstrip->host()); | |
914 int to = model_->IndexOfHost(dest_toolstrip->host()); | |
915 DCHECK(from != to); | |
916 model_->MoveToolstripAt(from, to); | |
917 } | |
918 | |
919 void ExtensionShelf::ExpandToolstrip(ExtensionHost* host, const GURL& url, | |
920 int height) { | |
921 ExtensionShelfModel::iterator toolstrip = model_->ToolstripForHost(host); | |
922 model_->ExpandToolstrip(toolstrip, url, height); | |
923 } | |
924 | |
925 void ExtensionShelf::CollapseToolstrip(ExtensionHost* host, const GURL& url) { | |
926 ExtensionShelfModel::iterator toolstrip = model_->ToolstripForHost(host); | |
927 model_->CollapseToolstrip(toolstrip, url); | |
928 } | |
929 | |
930 void ExtensionShelf::InitBackground(gfx::Canvas* canvas) { | |
931 if (!background_needs_repaint_) | |
932 return; | |
933 | |
934 // Capture a background bitmap to give to the toolstrips. | |
935 SkRect background_rect = { | |
936 SkIntToScalar(0), | |
937 SkIntToScalar(0), | |
938 SkIntToScalar(width()), | |
939 SkIntToScalar(height()) | |
940 }; | |
941 | |
942 // Tell all extension views about the new background. | |
943 int count = model_->count(); | |
944 for (int i = 0; i < count; ++i) { | |
945 ExtensionView* view = ToolstripAtIndex(i)->view(); | |
946 | |
947 const SkBitmap& background = | |
948 canvas->AsCanvasSkia()->getDevice()->accessBitmap(false); | |
949 | |
950 SkRect mapped_subset = background_rect; | |
951 gfx::Rect view_bounds = view->bounds(); | |
952 mapped_subset.offset(SkIntToScalar(view_bounds.x()), | |
953 SkIntToScalar(view_bounds.y())); | |
954 bool result = | |
955 canvas->AsCanvasSkia()->getTotalMatrix().mapRect(&mapped_subset); | |
956 DCHECK(result); | |
957 | |
958 SkIRect isubset; | |
959 mapped_subset.round(&isubset); | |
960 SkBitmap subset_bitmap; | |
961 // This will create another bitmap that just references pixels in the | |
962 // actual bitmap. | |
963 result = background.extractSubset(&subset_bitmap, isubset); | |
964 if (!result) | |
965 return; | |
966 | |
967 // We do a deep copy because extractSubset() returns a bitmap that | |
968 // references pixels in the original one and we want to actually make a | |
969 // smaller copy that will have a long lifetime. | |
970 SkBitmap smaller_copy; | |
971 if (!subset_bitmap.copyTo(&smaller_copy, SkBitmap::kARGB_8888_Config)) | |
972 return; | |
973 DCHECK(smaller_copy.readyToDraw()); | |
974 | |
975 view->SetBackground(smaller_copy); | |
976 } | |
977 | |
978 background_needs_repaint_ = false; | |
979 } | |
980 | |
981 ExtensionShelf::Toolstrip* ExtensionShelf::ToolstripAtX(int x) { | |
982 int count = model_->count(); | |
983 if (count == 0) | |
984 return NULL; | |
985 | |
986 if (x < 0) | |
987 return NULL; | |
988 | |
989 for (int i = 0; i < count; ++i) { | |
990 Toolstrip* toolstrip = ToolstripAtIndex(i); | |
991 View* view = toolstrip->GetShelfView(); | |
992 int x_mirrored = view->GetRootView()->MirroredXCoordinateInsideView(x); | |
993 if (x_mirrored > view->x() + view->width() + kToolstripPadding) | |
994 continue; | |
995 return toolstrip; | |
996 } | |
997 | |
998 return NULL; | |
999 } | |
1000 | |
1001 ExtensionShelf::Toolstrip* ExtensionShelf::ToolstripAtIndex(int index) { | |
1002 return static_cast<Toolstrip*>(model_->ToolstripAt(index).data); | |
1003 } | |
1004 | |
1005 ExtensionShelf::Toolstrip* ExtensionShelf::ToolstripForView( | |
1006 ExtensionView* view) { | |
1007 int count = model_->count(); | |
1008 for (int i = 0; i < count; ++i) { | |
1009 Toolstrip* toolstrip = ToolstripAtIndex(i); | |
1010 if (view == toolstrip->view()) | |
1011 return toolstrip; | |
1012 } | |
1013 return NULL; | |
1014 } | |
1015 | |
1016 void ExtensionShelf::LoadFromModel() { | |
1017 int count = model_->count(); | |
1018 for (int i = 0; i < count; ++i) | |
1019 ToolstripInsertedAt(model_->ToolstripAt(i).host, i); | |
1020 } | |
1021 | |
1022 gfx::Size ExtensionShelf::LayoutItems(bool compute_bounds_only) { | |
1023 if (!GetParent() || !model_ || !model_->count()) | |
1024 return gfx::Size(0, 0); | |
1025 | |
1026 gfx::Size prefsize; | |
1027 int x = kLeftMargin; | |
1028 int y = top_margin_; | |
1029 int content_height = kShelfHeight - top_margin_ - kBottomMargin; | |
1030 int max_x = width() - kRightMargin; | |
1031 | |
1032 if (OnNewTabPage()) { | |
1033 double current_state = 1 - size_animation_->GetCurrentValue(); | |
1034 x += static_cast<int>(static_cast<double> | |
1035 (kNewtabHorizontalPadding + kNewtabExtraHorMargin) * current_state); | |
1036 y += static_cast<int>(static_cast<double> | |
1037 (kNewtabVerticalPadding + kNewtabExtraVerMargin) * current_state); | |
1038 max_x -= static_cast<int>(static_cast<double> | |
1039 (2 * kNewtabHorizontalPadding) * current_state); | |
1040 } | |
1041 | |
1042 int count = model_->count(); | |
1043 for (int i = 0; i < count; ++i) { | |
1044 x += kToolstripPadding; // Left padding. | |
1045 Toolstrip* toolstrip = ToolstripAtIndex(i); | |
1046 if (!toolstrip) // Can be NULL while in the process of removing. | |
1047 continue; | |
1048 View* view = toolstrip->GetShelfView(); | |
1049 gfx::Size pref = view->GetPreferredSize(); | |
1050 | |
1051 // |next_x| is the x value for where the next toolstrip in the list will be. | |
1052 int next_x = x + pref.width() + kToolstripPadding; // Right padding. | |
1053 if (!compute_bounds_only) { | |
1054 bool clipped = next_x >= max_x; | |
1055 if (clipped) | |
1056 pref.set_width(std::max(0, max_x - x)); | |
1057 if (view == toolstrip->view()) | |
1058 toolstrip->view()->SetIsClipped(clipped); | |
1059 view->SetBounds(x, y, pref.width(), content_height); | |
1060 view->Layout(); | |
1061 if (toolstrip->window_visible()) | |
1062 toolstrip->LayoutWindow(); | |
1063 } | |
1064 x = next_x + kToolstripDividerWidth; | |
1065 } | |
1066 | |
1067 if (!compute_bounds_only) { | |
1068 background_needs_repaint_ = true; | |
1069 SchedulePaint(); | |
1070 } else { | |
1071 if (OnNewTabPage()) { | |
1072 prefsize.set_height(kShelfHeight + static_cast<int>(static_cast<double> | |
1073 (kNewtabShelfHeight - kShelfHeight) * | |
1074 (1 - size_animation_->GetCurrentValue()))); | |
1075 } else { | |
1076 prefsize.set_height(static_cast<int>(static_cast<double>(kShelfHeight) * | |
1077 size_animation_->GetCurrentValue())); | |
1078 } | |
1079 | |
1080 x += kRightMargin; | |
1081 prefsize.set_width(x); | |
1082 } | |
1083 | |
1084 return prefsize; | |
1085 } | |
1086 | |
1087 bool ExtensionShelf::IsDetached() const { | |
1088 return OnNewTabPage() && (size_animation_->GetCurrentValue() != 1); | |
1089 } | |
1090 | |
1091 bool ExtensionShelf::IsAlwaysShown() const { | |
1092 Profile* profile = browser_->profile(); | |
1093 return profile->GetPrefs()->GetBoolean(prefs::kShowExtensionShelf); | |
1094 } | |
1095 | |
1096 bool ExtensionShelf::OnNewTabPage() const { | |
1097 return (browser_ && browser_->GetSelectedTabContents() && | |
1098 browser_->GetSelectedTabContents()->IsExtensionShelfAlwaysVisible()); | |
1099 } | |
1100 | |
1101 void ExtensionShelf::OnFullscreenToggled(bool fullscreen) { | |
1102 if (fullscreen == fullscreen_) | |
1103 return; | |
1104 fullscreen_ = fullscreen; | |
1105 if (!IsAlwaysShown()) | |
1106 return; | |
1107 size_animation_->Reset(fullscreen ? 0 : 1); | |
1108 } | |
OLD | NEW |