• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #include "chrome/browser/ui/panels/docked_panel_collection.h"
6 
7 #include <math.h>
8 
9 #include <algorithm>
10 #include <vector>
11 
12 #include "base/auto_reset.h"
13 #include "base/bind.h"
14 #include "base/logging.h"
15 #include "base/message_loop/message_loop.h"
16 #include "chrome/browser/chrome_notification_types.h"
17 #include "chrome/browser/ui/panels/panel_drag_controller.h"
18 #include "chrome/browser/ui/panels/panel_manager.h"
19 #include "chrome/browser/ui/panels/panel_mouse_watcher.h"
20 #include "content/public/browser/notification_service.h"
21 #include "content/public/browser/notification_source.h"
22 
23 namespace {
24 // Width of spacing around panel collection and the left/right edges of the
25 // screen.
26 const int kPanelCollectionLeftMargin = 6;
27 const int kPanelCollectionRightMargin = 24;
28 
29 // Occasionally some system, like Windows, might not bring up or down the bottom
30 // bar when the mouse enters or leaves the bottom screen area. This is the
31 // maximum time we will wait for the bottom bar visibility change notification.
32 // After the time expires, we bring up/down the titlebars as planned.
33 const int kMaxDelayWaitForBottomBarVisibilityChangeMs = 1000;
34 
35 // After focus changed, one panel lost active status, another got it,
36 // we refresh layout with a delay.
37 const int kRefreshLayoutAfterActivePanelChangeDelayMs = 600;  // arbitrary
38 
39 // As we refresh panel positions, some or all panels may move. We make sure
40 // we do not animate too many panels at once as this tends to perform poorly.
41 const int kNumPanelsToAnimateSimultaneously = 3;
42 
43 }  // namespace
44 
DockedPanelCollection(PanelManager * panel_manager)45 DockedPanelCollection::DockedPanelCollection(PanelManager* panel_manager)
46     : PanelCollection(PanelCollection::DOCKED),
47       panel_manager_(panel_manager),
48       minimized_panel_count_(0),
49       are_titlebars_up_(false),
50       minimizing_all_(false),
51       delayed_titlebar_action_(NO_ACTION),
52       titlebar_action_factory_(this),
53       refresh_action_factory_(this) {
54   panel_manager_->display_settings_provider()->AddDesktopBarObserver(this);
55   OnDisplayChanged();
56 }
57 
~DockedPanelCollection()58 DockedPanelCollection::~DockedPanelCollection() {
59   DCHECK(panels_.empty());
60   DCHECK_EQ(0, minimized_panel_count_);
61   panel_manager_->display_settings_provider()->RemoveDesktopBarObserver(this);
62 }
63 
OnDisplayChanged()64 void DockedPanelCollection::OnDisplayChanged() {
65   work_area_ =
66       panel_manager_->display_settings_provider()->GetPrimaryWorkArea();
67   work_area_.set_x(work_area_.x() + kPanelCollectionLeftMargin);
68   work_area_.set_width(work_area_.width() -
69       kPanelCollectionLeftMargin - kPanelCollectionRightMargin);
70 
71   if (panels_.empty())
72     return;
73 
74   for (Panels::const_iterator iter = panels_.begin();
75        iter != panels_.end(); ++iter) {
76     (*iter)->LimitSizeToWorkArea(work_area_);
77   }
78 
79   RefreshLayout();
80 }
81 
AddPanel(Panel * panel,PositioningMask positioning_mask)82 void DockedPanelCollection::AddPanel(Panel* panel,
83                                 PositioningMask positioning_mask) {
84   // This method does not handle minimized panels.
85   DCHECK_EQ(Panel::EXPANDED, panel->expansion_state());
86 
87   DCHECK(panel->initialized());
88   DCHECK_NE(this, panel->collection());
89   panel->set_collection(this);
90 
91   bool default_position = (positioning_mask & KNOWN_POSITION) == 0;
92   bool update_bounds = (positioning_mask & DO_NOT_UPDATE_BOUNDS) == 0;
93 
94   if (default_position) {
95     gfx::Size full_size = panel->full_size();
96     gfx::Point pt = GetDefaultPositionForPanel(full_size);
97     panel->SetPanelBounds(gfx::Rect(pt, full_size));
98     panels_.push_back(panel);
99   } else {
100     DCHECK(update_bounds);
101     int x = panel->GetBounds().x();
102     Panels::iterator iter = panels_.begin();
103     for (; iter != panels_.end(); ++iter)
104       if (x > (*iter)->GetBounds().x())
105         break;
106     panels_.insert(iter, panel);
107   }
108 
109   if (update_bounds) {
110     if ((positioning_mask & DELAY_LAYOUT_REFRESH) != 0)
111       ScheduleLayoutRefresh();
112     else
113       RefreshLayout();
114   }
115 }
116 
GetDefaultPositionForPanel(const gfx::Size & full_size) const117 gfx::Point DockedPanelCollection::GetDefaultPositionForPanel(
118     const gfx::Size& full_size) const {
119   int x = 0;
120   if (!panels_.empty() &&
121       panels_.back()->GetBounds().x() < work_area_.x()) {
122     // Panels go off screen. Make sure the default position will place
123     // the panel in view.
124     Panels::const_reverse_iterator iter = panels_.rbegin();
125     for (; iter != panels_.rend(); ++iter) {
126       if ((*iter)->GetBounds().x() >= work_area_.x()) {
127         x = (*iter)->GetBounds().x();
128         break;
129       }
130     }
131     // At least one panel should fit on the screen.
132     DCHECK(x > work_area_.x());
133   } else {
134     x = std::max(GetRightMostAvailablePosition() - full_size.width(),
135                  work_area_.x());
136   }
137   return gfx::Point(x, work_area_.bottom() - full_size.height());
138 }
139 
StartingRightPosition() const140 int DockedPanelCollection::StartingRightPosition() const {
141   return work_area_.right();
142 }
143 
GetRightMostAvailablePosition() const144 int DockedPanelCollection::GetRightMostAvailablePosition() const {
145   return panels_.empty() ? StartingRightPosition() :
146       (panels_.back()->GetBounds().x() - kPanelsHorizontalSpacing);
147 }
148 
RemovePanel(Panel * panel,RemovalReason reason)149 void DockedPanelCollection::RemovePanel(Panel* panel, RemovalReason reason) {
150   DCHECK_EQ(this, panel->collection());
151   panel->set_collection(NULL);
152 
153   // Optimize for the common case of removing the last panel.
154   DCHECK(!panels_.empty());
155   if (panels_.back() == panel) {
156     panels_.pop_back();
157 
158     // Update the saved panel placement if needed. This is because
159     // we might remove |saved_panel_placement_.left_panel|.
160     if (saved_panel_placement_.panel &&
161         saved_panel_placement_.left_panel == panel)
162       saved_panel_placement_.left_panel = NULL;
163 
164   } else {
165     Panels::iterator iter = find(panels_.begin(), panels_.end(), panel);
166     DCHECK(iter != panels_.end());
167     iter = panels_.erase(iter);
168 
169     // Update the saved panel placement if needed. This is because
170     // we might remove |saved_panel_placement_.left_panel|.
171     if (saved_panel_placement_.panel &&
172         saved_panel_placement_.left_panel == panel)
173       saved_panel_placement_.left_panel = *iter;
174   }
175 
176   if (panel->expansion_state() != Panel::EXPANDED)
177     UpdateMinimizedPanelCount();
178 
179   RefreshLayout();
180 }
181 
SavePanelPlacement(Panel * panel)182 void DockedPanelCollection::SavePanelPlacement(Panel* panel) {
183   DCHECK(!saved_panel_placement_.panel);
184 
185   saved_panel_placement_.panel = panel;
186 
187   // To recover panel to its original placement, we only need to track the panel
188   // that is placed after it.
189   Panels::iterator iter = find(panels_.begin(), panels_.end(), panel);
190   DCHECK(iter != panels_.end());
191   ++iter;
192   saved_panel_placement_.left_panel = (iter == panels_.end()) ? NULL : *iter;
193 }
194 
RestorePanelToSavedPlacement()195 void DockedPanelCollection::RestorePanelToSavedPlacement() {
196   DCHECK(saved_panel_placement_.panel);
197 
198   Panel* panel = saved_panel_placement_.panel;
199 
200   // Find next panel after this panel.
201   Panels::iterator iter = std::find(panels_.begin(), panels_.end(), panel);
202   DCHECK(iter != panels_.end());
203   Panels::iterator next_iter = iter;
204   next_iter++;
205   Panel* next_panel = (next_iter == panels_.end()) ? NULL : *iter;
206 
207   // Restoring is only needed when this panel is not in the right position.
208   if (next_panel != saved_panel_placement_.left_panel) {
209     // Remove this panel from its current position.
210     panels_.erase(iter);
211 
212     // Insert this panel into its previous position.
213     if (saved_panel_placement_.left_panel) {
214       Panels::iterator iter_to_insert_before = std::find(panels_.begin(),
215           panels_.end(), saved_panel_placement_.left_panel);
216       DCHECK(iter_to_insert_before != panels_.end());
217       panels_.insert(iter_to_insert_before, panel);
218     } else {
219       panels_.push_back(panel);
220     }
221   }
222 
223   RefreshLayout();
224 
225   DiscardSavedPanelPlacement();
226 }
227 
DiscardSavedPanelPlacement()228 void DockedPanelCollection::DiscardSavedPanelPlacement() {
229   DCHECK(saved_panel_placement_.panel);
230   saved_panel_placement_.panel = NULL;
231   saved_panel_placement_.left_panel = NULL;
232 }
233 
GetPanelResizability(const Panel * panel) const234 panel::Resizability DockedPanelCollection::GetPanelResizability(
235     const Panel* panel) const {
236   return (panel->expansion_state() == Panel::EXPANDED) ?
237       panel::RESIZABLE_EXCEPT_BOTTOM : panel::NOT_RESIZABLE;
238 }
239 
OnPanelResizedByMouse(Panel * panel,const gfx::Rect & new_bounds)240 void DockedPanelCollection::OnPanelResizedByMouse(Panel* panel,
241                                                   const gfx::Rect& new_bounds) {
242   DCHECK_EQ(this, panel->collection());
243   panel->set_full_size(new_bounds.size());
244 }
245 
OnPanelExpansionStateChanged(Panel * panel)246 void DockedPanelCollection::OnPanelExpansionStateChanged(Panel* panel) {
247   gfx::Rect panel_bounds = panel->GetBounds();
248   AdjustPanelBoundsPerExpansionState(panel, &panel_bounds);
249   panel->SetPanelBounds(panel_bounds);
250 
251   UpdateMinimizedPanelCount();
252 
253   // Ensure minimized panel does not get the focus. If minimizing all,
254   // the active panel will be deactivated once when all panels are minimized
255   // rather than per minimized panel.
256   if (panel->expansion_state() != Panel::EXPANDED && !minimizing_all_ &&
257       panel->IsActive()) {
258     panel->Deactivate();
259     // The layout will refresh itself in response
260     // to (de)activation notification.
261   }
262 }
263 
AdjustPanelBoundsPerExpansionState(Panel * panel,gfx::Rect * bounds)264 void DockedPanelCollection::AdjustPanelBoundsPerExpansionState(Panel* panel,
265                                                           gfx::Rect* bounds) {
266   Panel::ExpansionState expansion_state = panel->expansion_state();
267   switch (expansion_state) {
268     case Panel::EXPANDED:
269       bounds->set_height(panel->full_size().height());
270 
271       break;
272     case Panel::TITLE_ONLY:
273       bounds->set_height(panel->TitleOnlyHeight());
274 
275       break;
276     case Panel::MINIMIZED:
277       bounds->set_height(panel::kMinimizedPanelHeight);
278 
279       break;
280     default:
281       NOTREACHED();
282       break;
283   }
284 
285   int bottom = GetBottomPositionForExpansionState(expansion_state);
286   bounds->set_y(bottom - bounds->height());
287 }
288 
OnPanelAttentionStateChanged(Panel * panel)289 void DockedPanelCollection::OnPanelAttentionStateChanged(Panel* panel) {
290   DCHECK_EQ(this, panel->collection());
291   Panel::ExpansionState state = panel->expansion_state();
292   if (panel->IsDrawingAttention()) {
293     // Bring up the titlebar to get user's attention.
294     if (state == Panel::MINIMIZED)
295       panel->SetExpansionState(Panel::TITLE_ONLY);
296     return;
297   }
298 
299   // Panel is no longer drawing attention, but leave the panel in
300   // title-only mode if all titlebars are currently up.
301   if (state != Panel::TITLE_ONLY || are_titlebars_up_)
302     return;
303 
304   // Leave titlebar up if panel is being dragged.
305   if (panel_manager_->drag_controller()->dragging_panel() == panel)
306     return;
307 
308   // Leave titlebar up if mouse is in/below the panel.
309   const gfx::Point mouse_position =
310       panel_manager_->mouse_watcher()->GetMousePosition();
311   gfx::Rect bounds = panel->GetBounds();
312   if (bounds.x() <= mouse_position.x() &&
313       mouse_position.x() <= bounds.right() &&
314       mouse_position.y() >= bounds.y())
315     return;
316 
317   // Bring down the titlebar now that panel is not drawing attention.
318   panel->SetExpansionState(Panel::MINIMIZED);
319 }
320 
OnPanelTitlebarClicked(Panel * panel,panel::ClickModifier modifier)321 void DockedPanelCollection::OnPanelTitlebarClicked(Panel* panel,
322                                               panel::ClickModifier modifier) {
323   DCHECK_EQ(this, panel->collection());
324   if (!IsPanelMinimized(panel))
325     return;
326 
327   if (modifier == panel::APPLY_TO_ALL)
328     RestoreAll();
329   else
330     RestorePanel(panel);
331 }
332 
ActivatePanel(Panel * panel)333 void DockedPanelCollection::ActivatePanel(Panel* panel) {
334   DCHECK_EQ(this, panel->collection());
335 
336   // Make sure the panel is expanded when activated so the user input
337   // does not go into a collapsed window.
338   panel->SetExpansionState(Panel::EXPANDED);
339 
340   // If the layout needs to be refreshed, it will happen in response to
341   // the activation notification (and with a slight delay to let things settle).
342 }
343 
MinimizePanel(Panel * panel)344 void DockedPanelCollection::MinimizePanel(Panel* panel) {
345   DCHECK_EQ(this, panel->collection());
346 
347   if (panel->expansion_state() != Panel::EXPANDED)
348     return;
349 
350   panel->SetExpansionState(panel->IsDrawingAttention() ?
351       Panel::TITLE_ONLY : Panel::MINIMIZED);
352 }
353 
RestorePanel(Panel * panel)354 void DockedPanelCollection::RestorePanel(Panel* panel) {
355   DCHECK_EQ(this, panel->collection());
356   panel->SetExpansionState(Panel::EXPANDED);
357 }
358 
MinimizeAll()359 void DockedPanelCollection::MinimizeAll() {
360   // Set minimizing_all_ to prevent deactivation of each panel when it
361   // is minimized. See comments in OnPanelExpansionStateChanged.
362   base::AutoReset<bool> pin(&minimizing_all_, true);
363   Panel* minimized_active_panel = NULL;
364   for (Panels::const_iterator iter = panels_.begin();
365        iter != panels_.end(); ++iter) {
366     if ((*iter)->IsActive())
367       minimized_active_panel = *iter;
368     MinimizePanel(*iter);
369   }
370 
371   // When a single panel is minimized, it is deactivated to ensure that
372   // a minimized panel does not have focus. However, when minimizing all,
373   // the deactivation is only done once after all panels are minimized,
374   // rather than per minimized panel, both for efficiency and to avoid
375   // temporary activations of random not-yet-minimized panels.
376   if (minimized_active_panel) {
377     minimized_active_panel->Deactivate();
378     // Layout will be refreshed in response to (de)activation notification.
379   }
380 }
381 
RestoreAll()382 void DockedPanelCollection::RestoreAll() {
383   for (Panels::const_iterator iter = panels_.begin();
384        iter != panels_.end(); ++iter) {
385     RestorePanel(*iter);
386   }
387 }
388 
OnMinimizeButtonClicked(Panel * panel,panel::ClickModifier modifier)389 void DockedPanelCollection::OnMinimizeButtonClicked(
390     Panel* panel, panel::ClickModifier modifier) {
391   if (modifier == panel::APPLY_TO_ALL)
392     MinimizeAll();
393   else
394     MinimizePanel(panel);
395 }
396 
OnRestoreButtonClicked(Panel * panel,panel::ClickModifier modifier)397 void DockedPanelCollection::OnRestoreButtonClicked(
398     Panel* panel, panel::ClickModifier modifier) {
399   if (modifier == panel::APPLY_TO_ALL)
400     RestoreAll();
401   else
402     RestorePanel(panel);
403 }
404 
CanShowMinimizeButton(const Panel * panel) const405 bool DockedPanelCollection::CanShowMinimizeButton(const Panel* panel) const {
406   return !IsPanelMinimized(panel);
407 }
408 
CanShowRestoreButton(const Panel * panel) const409 bool DockedPanelCollection::CanShowRestoreButton(const Panel* panel) const {
410   return IsPanelMinimized(panel);
411 }
412 
IsPanelMinimized(const Panel * panel) const413 bool DockedPanelCollection::IsPanelMinimized(const Panel* panel) const {
414   return panel->expansion_state() != Panel::EXPANDED;
415 }
416 
UsesAlwaysOnTopPanels() const417 bool DockedPanelCollection::UsesAlwaysOnTopPanels() const {
418   return true;
419 }
420 
UpdateMinimizedPanelCount()421 void DockedPanelCollection::UpdateMinimizedPanelCount() {
422   int prev_minimized_panel_count = minimized_panel_count_;
423   minimized_panel_count_ = 0;
424   for (Panels::const_iterator panel_iter = panels_.begin();
425         panel_iter != panels_.end(); ++panel_iter) {
426     if ((*panel_iter)->expansion_state() != Panel::EXPANDED)
427       minimized_panel_count_++;
428   }
429 
430   if (prev_minimized_panel_count == 0 && minimized_panel_count_ > 0)
431     panel_manager_->mouse_watcher()->AddObserver(this);
432   else if (prev_minimized_panel_count > 0 &&  minimized_panel_count_ == 0)
433     panel_manager_->mouse_watcher()->RemoveObserver(this);
434 
435   DCHECK_LE(minimized_panel_count_, num_panels());
436 }
437 
ResizePanelWindow(Panel * panel,const gfx::Size & preferred_window_size)438 void DockedPanelCollection::ResizePanelWindow(
439     Panel* panel,
440     const gfx::Size& preferred_window_size) {
441   DCHECK_EQ(this, panel->collection());
442   // Make sure the new size does not violate panel's size restrictions.
443   gfx::Size new_size(preferred_window_size.width(),
444                      preferred_window_size.height());
445   new_size = panel->ClampSize(new_size);
446 
447   if (new_size == panel->full_size())
448     return;
449 
450   panel->set_full_size(new_size);
451 
452   RefreshLayout();
453 }
454 
ShouldBringUpTitlebars(int mouse_x,int mouse_y) const455 bool DockedPanelCollection::ShouldBringUpTitlebars(int mouse_x,
456                                                    int mouse_y) const {
457   // We should always bring up the titlebar if the mouse is over the
458   // visible auto-hiding bottom bar.
459   DisplaySettingsProvider* provider =
460       panel_manager_->display_settings_provider();
461   if (provider->IsAutoHidingDesktopBarEnabled(
462           DisplaySettingsProvider::DESKTOP_BAR_ALIGNED_BOTTOM) &&
463       provider->GetDesktopBarVisibility(
464           DisplaySettingsProvider::DESKTOP_BAR_ALIGNED_BOTTOM) ==
465               DisplaySettingsProvider::DESKTOP_BAR_VISIBLE) {
466     int bottom_bar_bottom = work_area_.bottom();
467     int bottom_bar_y = bottom_bar_bottom - provider->GetDesktopBarThickness(
468         DisplaySettingsProvider::DESKTOP_BAR_ALIGNED_BOTTOM);
469     if (bottom_bar_y <= mouse_y && mouse_y <= bottom_bar_bottom)
470       return true;
471   }
472 
473   // Bring up titlebars if any panel needs the titlebar up.
474   Panel* dragging_panel = panel_manager_->drag_controller()->dragging_panel();
475   if (dragging_panel &&
476       dragging_panel->collection()->type() != PanelCollection::DOCKED)
477     dragging_panel = NULL;
478   for (Panels::const_iterator iter = panels_.begin();
479        iter != panels_.end(); ++iter) {
480     Panel* panel = *iter;
481     Panel::ExpansionState state = panel->expansion_state();
482     // Skip the expanded panel.
483     if (state == Panel::EXPANDED)
484       continue;
485 
486     // If the panel is showing titlebar only, we want to keep it up when it is
487     // being dragged.
488     if (state == Panel::TITLE_ONLY && panel == dragging_panel)
489       return true;
490 
491     // We do not want to bring up other minimized panels if the mouse is over
492     // the panel that pops up the titlebar to attract attention.
493     if (panel->IsDrawingAttention())
494       continue;
495 
496     gfx::Rect bounds = panel->GetBounds();
497     if (bounds.x() <= mouse_x && mouse_x <= bounds.right() &&
498         mouse_y >= bounds.y())
499       return true;
500   }
501   return false;
502 }
503 
BringUpOrDownTitlebars(bool bring_up)504 void DockedPanelCollection::BringUpOrDownTitlebars(bool bring_up) {
505   if (are_titlebars_up_ == bring_up)
506     return;
507 
508   are_titlebars_up_ = bring_up;
509   int task_delay_ms = 0;
510 
511   // If the auto-hiding bottom bar exists, delay the action until the bottom
512   // bar is fully visible or hidden. We do not want both bottom bar and panel
513   // titlebar to move at the same time but with different speeds.
514   DisplaySettingsProvider* provider =
515       panel_manager_->display_settings_provider();
516   if (provider->IsAutoHidingDesktopBarEnabled(
517           DisplaySettingsProvider::DESKTOP_BAR_ALIGNED_BOTTOM)) {
518     DisplaySettingsProvider::DesktopBarVisibility visibility =
519         provider->GetDesktopBarVisibility(
520             DisplaySettingsProvider::DESKTOP_BAR_ALIGNED_BOTTOM);
521     if (visibility !=
522         (bring_up ? DisplaySettingsProvider::DESKTOP_BAR_VISIBLE
523                   : DisplaySettingsProvider::DESKTOP_BAR_HIDDEN)) {
524       // Occasionally some system, like Windows, might not bring up or down the
525       // bottom bar when the mouse enters or leaves the bottom screen area.
526       // Thus, we schedule a delayed task to do the work if we do not receive
527       // the bottom bar visibility change notification within a certain period
528       // of time.
529       task_delay_ms = kMaxDelayWaitForBottomBarVisibilityChangeMs;
530     }
531   }
532 
533   // OnAutoHidingDesktopBarVisibilityChanged will handle this.
534   delayed_titlebar_action_ = bring_up ? BRING_UP : BRING_DOWN;
535 
536   // If user moves the mouse in and out of mouse tracking area, we might have
537   // previously posted but not yet dispatched task in the queue. New action
538   // should always 'reset' the delays so cancel any tasks that haven't run yet
539   // and post a new one.
540   titlebar_action_factory_.InvalidateWeakPtrs();
541   base::MessageLoop::current()->PostDelayedTask(
542       FROM_HERE,
543       base::Bind(&DockedPanelCollection::DelayedBringUpOrDownTitlebarsCheck,
544                  titlebar_action_factory_.GetWeakPtr()),
545       base::TimeDelta::FromMilliseconds(
546           PanelManager::AdjustTimeInterval(task_delay_ms)));
547 }
548 
DelayedBringUpOrDownTitlebarsCheck()549 void DockedPanelCollection::DelayedBringUpOrDownTitlebarsCheck() {
550   // Task was already processed or cancelled - bail out.
551   if (delayed_titlebar_action_ == NO_ACTION)
552     return;
553 
554   bool need_to_bring_up_titlebars = (delayed_titlebar_action_ == BRING_UP);
555 
556   delayed_titlebar_action_ = NO_ACTION;
557 
558   // Check if the action is still needed based on the latest mouse position. The
559   // user could move the mouse into the tracking area and then quickly move it
560   // out of the area. In case of this, cancel the action.
561   if (are_titlebars_up_ != need_to_bring_up_titlebars)
562     return;
563 
564   DoBringUpOrDownTitlebars(need_to_bring_up_titlebars);
565 }
566 
DoBringUpOrDownTitlebars(bool bring_up)567 void DockedPanelCollection::DoBringUpOrDownTitlebars(bool bring_up) {
568   for (Panels::const_iterator iter = panels_.begin();
569        iter != panels_.end(); ++iter) {
570     Panel* panel = *iter;
571 
572     // Skip any panel that is drawing the attention.
573     if (panel->IsDrawingAttention())
574       continue;
575 
576     if (bring_up) {
577       if (panel->expansion_state() == Panel::MINIMIZED)
578         panel->SetExpansionState(Panel::TITLE_ONLY);
579     } else {
580       if (panel->expansion_state() == Panel::TITLE_ONLY)
581         panel->SetExpansionState(Panel::MINIMIZED);
582     }
583   }
584 }
585 
GetBottomPositionForExpansionState(Panel::ExpansionState expansion_state) const586 int DockedPanelCollection::GetBottomPositionForExpansionState(
587     Panel::ExpansionState expansion_state) const {
588   int bottom = work_area_.bottom();
589   // If there is an auto-hiding desktop bar aligned to the bottom edge, we need
590   // to move the title-only panel above the auto-hiding desktop bar.
591   DisplaySettingsProvider* provider =
592       panel_manager_->display_settings_provider();
593   if (expansion_state == Panel::TITLE_ONLY &&
594       provider->IsAutoHidingDesktopBarEnabled(
595           DisplaySettingsProvider::DESKTOP_BAR_ALIGNED_BOTTOM)) {
596     bottom -= provider->GetDesktopBarThickness(
597         DisplaySettingsProvider::DESKTOP_BAR_ALIGNED_BOTTOM);
598   }
599 
600   return bottom;
601 }
602 
OnMouseMove(const gfx::Point & mouse_position)603 void DockedPanelCollection::OnMouseMove(const gfx::Point& mouse_position) {
604   bool bring_up_titlebars = ShouldBringUpTitlebars(mouse_position.x(),
605                                                    mouse_position.y());
606   BringUpOrDownTitlebars(bring_up_titlebars);
607 }
608 
OnAutoHidingDesktopBarVisibilityChanged(DisplaySettingsProvider::DesktopBarAlignment alignment,DisplaySettingsProvider::DesktopBarVisibility visibility)609 void DockedPanelCollection::OnAutoHidingDesktopBarVisibilityChanged(
610     DisplaySettingsProvider::DesktopBarAlignment alignment,
611     DisplaySettingsProvider::DesktopBarVisibility visibility) {
612   if (delayed_titlebar_action_ == NO_ACTION)
613     return;
614 
615   DisplaySettingsProvider::DesktopBarVisibility expected_visibility =
616       delayed_titlebar_action_ == BRING_UP
617           ? DisplaySettingsProvider::DESKTOP_BAR_VISIBLE
618           : DisplaySettingsProvider::DESKTOP_BAR_HIDDEN;
619   if (visibility != expected_visibility)
620     return;
621 
622   DoBringUpOrDownTitlebars(delayed_titlebar_action_ == BRING_UP);
623   delayed_titlebar_action_ = NO_ACTION;
624 }
625 
OnAutoHidingDesktopBarThicknessChanged(DisplaySettingsProvider::DesktopBarAlignment alignment,int thickness)626 void DockedPanelCollection::OnAutoHidingDesktopBarThicknessChanged(
627     DisplaySettingsProvider::DesktopBarAlignment alignment, int thickness) {
628   RefreshLayout();
629 }
630 
RefreshLayout()631 void DockedPanelCollection::RefreshLayout() {
632   int total_active_width = 0;
633   int total_inactive_width = 0;
634 
635   for (Panels::const_iterator panel_iter = panels_.begin();
636        panel_iter != panels_.end(); ++panel_iter) {
637     Panel* panel = *panel_iter;
638     if (panel->IsActive())
639       total_active_width += panel->full_size().width();
640     else
641       total_inactive_width += panel->full_size().width();
642   }
643 
644   double display_width_for_inactive_panels =
645       work_area_.width() - total_active_width -
646           kPanelsHorizontalSpacing * panels_.size();
647   double overflow_squeeze_factor = (total_inactive_width > 0) ?
648       std::min(display_width_for_inactive_panels / total_inactive_width, 1.0) :
649       1.0;
650 
651   // We want to calculate all bounds first, then apply them in a specific order.
652   typedef std::pair<Panel*, gfx::Rect> PanelBoundsInfo;
653   // The next pair of variables will hold panels that move, respectively,
654   // to the right and to the left. We want to process them from the center
655   // outwards, so one is a stack and another is a queue.
656   std::vector<PanelBoundsInfo> moving_right;
657   std::queue<PanelBoundsInfo> moving_left;
658 
659   int rightmost_position = StartingRightPosition();
660   for (Panels::const_iterator panel_iter = panels_.begin();
661        panel_iter != panels_.end(); ++panel_iter) {
662     Panel* panel = *panel_iter;
663     gfx::Rect old_bounds = panel->GetBounds();
664     gfx::Rect new_bounds = old_bounds;
665     AdjustPanelBoundsPerExpansionState(panel, &new_bounds);
666 
667     new_bounds.set_width(
668       WidthToDisplayPanelInCollection(panel->IsActive(),
669                                       overflow_squeeze_factor,
670                                       panel->full_size().width()));
671     int x = rightmost_position - new_bounds.width();
672     new_bounds.set_x(x);
673 
674     if (x < old_bounds.x() ||
675         (x == old_bounds.x() && new_bounds.width() <= old_bounds.width()))
676       moving_left.push(std::make_pair(panel, new_bounds));
677     else
678       moving_right.push_back(std::make_pair(panel, new_bounds));
679 
680     rightmost_position = x - kPanelsHorizontalSpacing;
681   }
682 
683   // Update panels going in both directions.
684   // This is important on Mac where bounds changes are slow and you see a
685   // "wave" instead of a smooth sliding effect.
686   int num_animated = 0;
687   bool going_right = true;
688   while (!moving_right.empty() || !moving_left.empty()) {
689     PanelBoundsInfo bounds_info;
690     // Alternate between processing the panels that moving left and right,
691     // starting from the center.
692     going_right = !going_right;
693     bool take_panel_on_right =
694         (going_right && !moving_right.empty()) ||
695         moving_left.empty();
696     if (take_panel_on_right) {
697       bounds_info = moving_right.back();
698       moving_right.pop_back();
699     } else {
700       bounds_info = moving_left.front();
701       moving_left.pop();
702     }
703 
704     // Don't update the docked panel that is in preview mode.
705     Panel* panel = bounds_info.first;
706     gfx::Rect bounds = bounds_info.second;
707     if (!panel->in_preview_mode() && bounds != panel->GetBounds()) {
708       // We animate a limited number of panels, starting with the
709       // "most important" ones, that is, ones that are close to the center
710       // of the action. Other panels are moved instantly to improve performance.
711       if (num_animated < kNumPanelsToAnimateSimultaneously) {
712         panel->SetPanelBounds(bounds);  // Animates.
713         ++num_animated;
714       } else {
715         panel->SetPanelBoundsInstantly(bounds);
716       }
717     }
718   }
719 
720   content::NotificationService::current()->Notify(
721       chrome::NOTIFICATION_PANEL_COLLECTION_UPDATED,
722       content::Source<PanelCollection>(this),
723       content::NotificationService::NoDetails());
724 }
725 
WidthToDisplayPanelInCollection(bool is_for_active_panel,double squeeze_factor,int full_width) const726 int DockedPanelCollection::WidthToDisplayPanelInCollection(
727     bool is_for_active_panel, double squeeze_factor, int full_width) const {
728   return is_for_active_panel ? full_width :
729       std::max(panel::kPanelMinWidth,
730                static_cast<int>(floor(full_width * squeeze_factor)));
731 }
732 
CloseAll()733 void DockedPanelCollection::CloseAll() {
734   // This should only be called at the end of tests to clean up.
735 
736   // Make a copy of the iterator as closing panels can modify the vector.
737   Panels panels_copy = panels_;
738 
739   // Start from the bottom to avoid reshuffling.
740   for (Panels::reverse_iterator iter = panels_copy.rbegin();
741        iter != panels_copy.rend(); ++iter)
742     (*iter)->Close();
743 }
744 
UpdatePanelOnCollectionChange(Panel * panel)745 void DockedPanelCollection::UpdatePanelOnCollectionChange(Panel* panel) {
746   panel->set_attention_mode(Panel::USE_PANEL_ATTENTION);
747   panel->ShowShadow(true);
748   panel->UpdateMinimizeRestoreButtonVisibility();
749   panel->SetWindowCornerStyle(panel::TOP_ROUNDED);
750 }
751 
ScheduleLayoutRefresh()752 void DockedPanelCollection::ScheduleLayoutRefresh() {
753   refresh_action_factory_.InvalidateWeakPtrs();
754   base::MessageLoop::current()->PostDelayedTask(
755       FROM_HERE,
756       base::Bind(&DockedPanelCollection::RefreshLayout,
757                  refresh_action_factory_.GetWeakPtr()),
758       base::TimeDelta::FromMilliseconds(PanelManager::AdjustTimeInterval(
759           kRefreshLayoutAfterActivePanelChangeDelayMs)));
760 }
761 
OnPanelActiveStateChanged(Panel * panel)762 void DockedPanelCollection::OnPanelActiveStateChanged(Panel* panel) {
763   // Refresh layout, but wait till active states settle.
764   // This lets us avoid refreshing too many times when one panel loses
765   // focus and another gains it.
766   ScheduleLayoutRefresh();
767 }
768 
GetInitialPanelBounds(const gfx::Rect & requested_bounds) const769 gfx::Rect DockedPanelCollection::GetInitialPanelBounds(
770       const gfx::Rect& requested_bounds) const {
771   gfx::Rect initial_bounds = requested_bounds;
772   initial_bounds.set_origin(
773       GetDefaultPositionForPanel(requested_bounds.size()));
774   return initial_bounds;
775 }
776 
HasPanel(Panel * panel) const777 bool DockedPanelCollection::HasPanel(Panel* panel) const {
778   return find(panels_.begin(), panels_.end(), panel) != panels_.end();
779 }
780