1// Copyright (c) 2013 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/gtk/panels/panel_stack_window_gtk.h"
6
7#include <gdk/gdkkeysyms.h>
8#include "base/strings/utf_string_conversions.h"
9#include "chrome/browser/ui/panels/panel.h"
10#include "chrome/browser/ui/panels/stacked_panel_collection.h"
11#include "ui/base/x/active_window_watcher_x.h"
12
13// static
14NativePanelStackWindow* NativePanelStackWindow::Create(
15    NativePanelStackWindowDelegate* delegate) {
16  return new PanelStackWindowGtk(delegate);
17}
18
19PanelStackWindowGtk::PanelStackWindowGtk(
20   NativePanelStackWindowDelegate* delegate)
21   : delegate_(delegate),
22     window_(NULL),
23     is_minimized_(false),
24     bounds_updates_started_(false) {
25  ui::ActiveWindowWatcherX::AddObserver(this);
26}
27
28PanelStackWindowGtk::~PanelStackWindowGtk() {
29  ui::ActiveWindowWatcherX::RemoveObserver(this);
30}
31
32void PanelStackWindowGtk::Close() {
33  if (!window_)
34    return;
35  gtk_widget_destroy(GTK_WIDGET(window_));
36  window_ = NULL;
37}
38
39void PanelStackWindowGtk::AddPanel(Panel* panel) {
40  panels_.push_back(panel);
41
42  EnsureWindowCreated();
43  SetStackWindowBounds();
44
45  // The panel being stacked should not appear on the taskbar.
46  gtk_window_set_skip_taskbar_hint(panel->GetNativeWindow(), true);
47}
48
49void PanelStackWindowGtk::RemovePanel(Panel* panel) {
50  panels_.remove(panel);
51
52  SetStackWindowBounds();
53
54  // The panel being unstacked should re-appear on the taskbar.
55  // Note that the underlying gtk window is gone when the panel is being
56  // closed.
57  GtkWindow* gtk_window = panel->GetNativeWindow();
58  if (gtk_window)
59    gtk_window_set_skip_taskbar_hint(gtk_window, false);
60}
61
62void PanelStackWindowGtk::MergeWith(NativePanelStackWindow* another) {
63  PanelStackWindowGtk* another_stack =
64      static_cast<PanelStackWindowGtk*>(another);
65  for (Panels::const_iterator iter = another_stack->panels_.begin();
66       iter != another_stack->panels_.end(); ++iter) {
67    Panel* panel = *iter;
68    panels_.push_back(panel);
69  }
70  another_stack->panels_.clear();
71
72  SetStackWindowBounds();
73}
74
75bool PanelStackWindowGtk::IsEmpty() const {
76  return panels_.empty();
77}
78
79bool PanelStackWindowGtk::HasPanel(Panel* panel) const {
80  return std::find(panels_.begin(), panels_.end(), panel) != panels_.end();
81}
82
83void PanelStackWindowGtk::MovePanelsBy(const gfx::Vector2d& delta) {
84  for (Panels::const_iterator iter = panels_.begin();
85       iter != panels_.end(); ++iter) {
86    Panel* panel = *iter;
87    gfx::Rect bounds = panel->GetBounds();
88    bounds.Offset(delta);
89    panel->SetPanelBoundsInstantly(bounds);
90  }
91
92  SetStackWindowBounds();
93}
94
95void PanelStackWindowGtk::BeginBatchUpdatePanelBounds(bool animate) {
96  // Bounds animation is not supported on GTK.
97  bounds_updates_started_ = true;
98}
99
100void PanelStackWindowGtk::AddPanelBoundsForBatchUpdate(
101    Panel* panel, const gfx::Rect& new_bounds) {
102  DCHECK(bounds_updates_started_);
103
104  // No need to track it if no change is needed.
105  if (panel->GetBounds() == new_bounds)
106    return;
107
108  // New bounds are stored as the map value.
109  bounds_updates_[panel] = new_bounds;
110}
111
112void PanelStackWindowGtk::EndBatchUpdatePanelBounds() {
113  DCHECK(bounds_updates_started_);
114
115  bounds_updates_started_ = false;
116
117  for (BoundsUpdates::const_iterator iter = bounds_updates_.begin();
118       iter != bounds_updates_.end(); ++iter) {
119    iter->first->SetPanelBoundsInstantly(iter->second);
120  }
121  bounds_updates_.clear();
122
123  SetStackWindowBounds();
124
125  delegate_->PanelBoundsBatchUpdateCompleted();
126}
127
128bool PanelStackWindowGtk::IsAnimatingPanelBounds() const {
129  return bounds_updates_started_;
130}
131
132void PanelStackWindowGtk::Minimize() {
133  gtk_window_iconify(window_);
134}
135
136bool PanelStackWindowGtk::IsMinimized() const {
137  return is_minimized_;
138}
139
140void PanelStackWindowGtk::DrawSystemAttention(bool draw_attention) {
141  gtk_window_set_urgency_hint(window_, draw_attention);
142}
143
144void PanelStackWindowGtk::OnPanelActivated(Panel* panel) {
145  // If a panel in a stack is activated, make sure all other panels in the stack
146  // are brought to the top in the z-order.
147  for (Panels::const_iterator iter = panels_.begin();
148       iter != panels_.end(); ++iter) {
149    GtkWindow* gtk_window = (*iter)->GetNativeWindow();
150    if (gtk_window) {
151      GdkWindow* gdk_window = gtk_widget_get_window(GTK_WIDGET(gtk_window));
152      gdk_window_raise(gdk_window);
153    }
154  }
155}
156
157void PanelStackWindowGtk::ActiveWindowChanged(GdkWindow* active_window) {
158  // Bail out if icewm is detected. This is because icewm always creates a
159  // window as active and we do not want to perform the logic here to
160  // activate a panel window when the background window is being created.
161  if (ui::GuessWindowManager() == ui::WM_ICE_WM)
162    return;
163
164  if (!window_ || panels_.empty())
165    return;
166
167  // The background stack window is activated when its taskbar icon is clicked.
168  // When this occurs, we need to activate the most recently active panel.
169  if (gtk_widget_get_window(GTK_WIDGET(window_)) == active_window) {
170    Panel* panel_to_focus =
171        panels_.front()->stack()->most_recently_active_panel();
172    if (panel_to_focus)
173      panel_to_focus->Activate();
174  }
175}
176
177gboolean PanelStackWindowGtk::OnWindowDeleteEvent(GtkWidget* widget,
178                                                  GdkEvent* event) {
179  DCHECK(!panels_.empty());
180
181  // Make a copy since closing a panel could modify the list.
182  Panels panels_copy = panels_;
183  for (Panels::const_iterator iter = panels_copy.begin();
184       iter != panels_copy.end(); ++iter) {
185    (*iter)->Close();
186  }
187
188  // Return true to prevent the gtk window from being destroyed.  Close will
189  // destroy it for us.
190  return TRUE;
191}
192
193gboolean PanelStackWindowGtk::OnWindowState(GtkWidget* widget,
194                                            GdkEventWindowState* event) {
195  bool is_minimized = event->new_window_state & GDK_WINDOW_STATE_ICONIFIED;
196  if (is_minimized_ == is_minimized)
197    return FALSE;
198  is_minimized_ = is_minimized;
199
200  for (Panels::const_iterator iter = panels_.begin();
201       iter != panels_.end(); ++iter) {
202    GtkWindow* gtk_window = (*iter)->GetNativeWindow();
203    if (is_minimized_)
204      gtk_window_iconify(gtk_window);
205    else
206      gtk_window_deiconify(gtk_window);
207  }
208
209  return FALSE;
210}
211
212void PanelStackWindowGtk::EnsureWindowCreated() {
213  if (window_)
214    return;
215
216  DCHECK(!panels_.empty());
217  Panel* panel = panels_.front();
218
219  // Create a small window that stays behinds the panels.
220  window_ = GTK_WINDOW(gtk_window_new(GTK_WINDOW_TOPLEVEL));
221  gtk_window_set_decorated(window_, false);
222  gtk_window_set_resizable(window_, false);
223  gtk_window_set_focus_on_map(window_, false);
224  gtk_widget_show(GTK_WIDGET(window_));
225  gdk_window_move_resize(gtk_widget_get_window(GTK_WIDGET(window_)),
226      panel->GetBounds().x(), panel->GetBounds().y(), 1, 1);
227  gdk_window_lower(gtk_widget_get_window(GTK_WIDGET(window_)));
228
229  // Connect signal handlers to the window.
230  g_signal_connect(window_, "delete-event",
231                   G_CALLBACK(OnWindowDeleteEventThunk), this);
232  g_signal_connect(window_, "window-state-event",
233                   G_CALLBACK(OnWindowStateThunk), this);
234
235  // Should appear on the taskbar.
236  gtk_window_set_skip_taskbar_hint(window_, false);
237
238  // Set the window icon and title.
239  string16 title = delegate_->GetTitle();
240  gtk_window_set_title(window_, UTF16ToUTF8(title).c_str());
241
242  gfx::Image app_icon = panel->app_icon();
243  if (!app_icon.IsEmpty())
244    gtk_window_set_icon(window_, app_icon.ToGdkPixbuf());
245}
246
247void PanelStackWindowGtk::SetStackWindowBounds() {
248  if (panels_.empty())
249    return;
250  Panel* panel = panels_.front();
251  // Position the small background window a bit away from the left-top corner
252  // such that it will be completely invisible.
253  gdk_window_move_resize(gtk_widget_get_window(GTK_WIDGET(window_)),
254      panel->GetBounds().x() + 5, panel->GetBounds().y() + 5, 1, 1);
255}
256