// 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. 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); }