blob: 6be87a6d8d12d2455b01775bdb036969af4f1adf [file] [log] [blame]
// Copyright (c) 2013 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/ui/gtk/panels/panel_stack_window_gtk.h"
#include <gdk/gdkkeysyms.h>
#include "base/strings/utf_string_conversions.h"
#include "chrome/browser/ui/panels/panel.h"
#include "chrome/browser/ui/panels/stacked_panel_collection.h"
#include "ui/base/x/active_window_watcher_x.h"
// static
NativePanelStackWindow* NativePanelStackWindow::Create(
NativePanelStackWindowDelegate* delegate) {
return new PanelStackWindowGtk(delegate);
}
PanelStackWindowGtk::PanelStackWindowGtk(
NativePanelStackWindowDelegate* delegate)
: delegate_(delegate),
window_(NULL),
is_minimized_(false),
bounds_updates_started_(false) {
ui::ActiveWindowWatcherX::AddObserver(this);
}
PanelStackWindowGtk::~PanelStackWindowGtk() {
ui::ActiveWindowWatcherX::RemoveObserver(this);
}
void PanelStackWindowGtk::Close() {
if (!window_)
return;
gtk_widget_destroy(GTK_WIDGET(window_));
window_ = NULL;
}
void PanelStackWindowGtk::AddPanel(Panel* panel) {
panels_.push_back(panel);
EnsureWindowCreated();
SetStackWindowBounds();
// The panel being stacked should not appear on the taskbar.
gtk_window_set_skip_taskbar_hint(panel->GetNativeWindow(), true);
}
void PanelStackWindowGtk::RemovePanel(Panel* panel) {
panels_.remove(panel);
SetStackWindowBounds();
// The panel being unstacked should re-appear on the taskbar.
// Note that the underlying gtk window is gone when the panel is being
// closed.
GtkWindow* gtk_window = panel->GetNativeWindow();
if (gtk_window)
gtk_window_set_skip_taskbar_hint(gtk_window, false);
}
void PanelStackWindowGtk::MergeWith(NativePanelStackWindow* another) {
PanelStackWindowGtk* another_stack =
static_cast<PanelStackWindowGtk*>(another);
for (Panels::const_iterator iter = another_stack->panels_.begin();
iter != another_stack->panels_.end(); ++iter) {
Panel* panel = *iter;
panels_.push_back(panel);
}
another_stack->panels_.clear();
SetStackWindowBounds();
}
bool PanelStackWindowGtk::IsEmpty() const {
return panels_.empty();
}
bool PanelStackWindowGtk::HasPanel(Panel* panel) const {
return std::find(panels_.begin(), panels_.end(), panel) != panels_.end();
}
void PanelStackWindowGtk::MovePanelsBy(const gfx::Vector2d& delta) {
for (Panels::const_iterator iter = panels_.begin();
iter != panels_.end(); ++iter) {
Panel* panel = *iter;
gfx::Rect bounds = panel->GetBounds();
bounds.Offset(delta);
panel->SetPanelBoundsInstantly(bounds);
}
SetStackWindowBounds();
}
void PanelStackWindowGtk::BeginBatchUpdatePanelBounds(bool animate) {
// Bounds animation is not supported on GTK.
bounds_updates_started_ = true;
}
void PanelStackWindowGtk::AddPanelBoundsForBatchUpdate(
Panel* panel, const gfx::Rect& new_bounds) {
DCHECK(bounds_updates_started_);
// No need to track it if no change is needed.
if (panel->GetBounds() == new_bounds)
return;
// New bounds are stored as the map value.
bounds_updates_[panel] = new_bounds;
}
void PanelStackWindowGtk::EndBatchUpdatePanelBounds() {
DCHECK(bounds_updates_started_);
bounds_updates_started_ = false;
for (BoundsUpdates::const_iterator iter = bounds_updates_.begin();
iter != bounds_updates_.end(); ++iter) {
iter->first->SetPanelBoundsInstantly(iter->second);
}
bounds_updates_.clear();
SetStackWindowBounds();
delegate_->PanelBoundsBatchUpdateCompleted();
}
bool PanelStackWindowGtk::IsAnimatingPanelBounds() const {
return bounds_updates_started_;
}
void PanelStackWindowGtk::Minimize() {
gtk_window_iconify(window_);
}
bool PanelStackWindowGtk::IsMinimized() const {
return is_minimized_;
}
void PanelStackWindowGtk::DrawSystemAttention(bool draw_attention) {
gtk_window_set_urgency_hint(window_, draw_attention);
}
void PanelStackWindowGtk::OnPanelActivated(Panel* panel) {
// If a panel in a stack is activated, make sure all other panels in the stack
// are brought to the top in the z-order.
for (Panels::const_iterator iter = panels_.begin();
iter != panels_.end(); ++iter) {
GtkWindow* gtk_window = (*iter)->GetNativeWindow();
if (gtk_window) {
GdkWindow* gdk_window = gtk_widget_get_window(GTK_WIDGET(gtk_window));
gdk_window_raise(gdk_window);
}
}
}
void PanelStackWindowGtk::ActiveWindowChanged(GdkWindow* active_window) {
// Bail out if icewm is detected. This is because icewm always creates a
// window as active and we do not want to perform the logic here to
// activate a panel window when the background window is being created.
if (ui::GuessWindowManager() == ui::WM_ICE_WM)
return;
if (!window_ || panels_.empty())
return;
// The background stack window is activated when its taskbar icon is clicked.
// When this occurs, we need to activate the most recently active panel.
if (gtk_widget_get_window(GTK_WIDGET(window_)) == active_window) {
Panel* panel_to_focus =
panels_.front()->stack()->most_recently_active_panel();
if (panel_to_focus)
panel_to_focus->Activate();
}
}
gboolean PanelStackWindowGtk::OnWindowDeleteEvent(GtkWidget* widget,
GdkEvent* event) {
DCHECK(!panels_.empty());
// Make a copy since closing a panel could modify the list.
Panels panels_copy = panels_;
for (Panels::const_iterator iter = panels_copy.begin();
iter != panels_copy.end(); ++iter) {
(*iter)->Close();
}
// Return true to prevent the gtk window from being destroyed. Close will
// destroy it for us.
return TRUE;
}
gboolean PanelStackWindowGtk::OnWindowState(GtkWidget* widget,
GdkEventWindowState* event) {
bool is_minimized = event->new_window_state & GDK_WINDOW_STATE_ICONIFIED;
if (is_minimized_ == is_minimized)
return FALSE;
is_minimized_ = is_minimized;
for (Panels::const_iterator iter = panels_.begin();
iter != panels_.end(); ++iter) {
GtkWindow* gtk_window = (*iter)->GetNativeWindow();
if (is_minimized_)
gtk_window_iconify(gtk_window);
else
gtk_window_deiconify(gtk_window);
}
return FALSE;
}
void PanelStackWindowGtk::EnsureWindowCreated() {
if (window_)
return;
DCHECK(!panels_.empty());
Panel* panel = panels_.front();
// Create a small window that stays behinds the panels.
window_ = GTK_WINDOW(gtk_window_new(GTK_WINDOW_TOPLEVEL));
gtk_window_set_decorated(window_, false);
gtk_window_set_resizable(window_, false);
gtk_window_set_focus_on_map(window_, false);
gtk_widget_show(GTK_WIDGET(window_));
gdk_window_move_resize(gtk_widget_get_window(GTK_WIDGET(window_)),
panel->GetBounds().x(), panel->GetBounds().y(), 1, 1);
gdk_window_lower(gtk_widget_get_window(GTK_WIDGET(window_)));
// Connect signal handlers to the window.
g_signal_connect(window_, "delete-event",
G_CALLBACK(OnWindowDeleteEventThunk), this);
g_signal_connect(window_, "window-state-event",
G_CALLBACK(OnWindowStateThunk), this);
// Should appear on the taskbar.
gtk_window_set_skip_taskbar_hint(window_, false);
// Set the window icon and title.
base::string16 title = delegate_->GetTitle();
gtk_window_set_title(window_, UTF16ToUTF8(title).c_str());
gfx::Image app_icon = delegate_->GetIcon();
if (!app_icon.IsEmpty())
gtk_window_set_icon(window_, app_icon.ToGdkPixbuf());
}
void PanelStackWindowGtk::SetStackWindowBounds() {
if (panels_.empty())
return;
Panel* panel = panels_.front();
// Position the small background window a bit away from the left-top corner
// such that it will be completely invisible.
gdk_window_move_resize(gtk_widget_get_window(GTK_WIDGET(window_)),
panel->GetBounds().x() + 5, panel->GetBounds().y() + 5, 1, 1);
}