Home | History | Annotate | Download | only in panels
      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
     14 NativePanelStackWindow* NativePanelStackWindow::Create(
     15     NativePanelStackWindowDelegate* delegate) {
     16   return new PanelStackWindowGtk(delegate);
     17 }
     18 
     19 PanelStackWindowGtk::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 
     28 PanelStackWindowGtk::~PanelStackWindowGtk() {
     29   ui::ActiveWindowWatcherX::RemoveObserver(this);
     30 }
     31 
     32 void PanelStackWindowGtk::Close() {
     33   if (!window_)
     34     return;
     35   gtk_widget_destroy(GTK_WIDGET(window_));
     36   window_ = NULL;
     37 }
     38 
     39 void 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 
     49 void 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 
     62 void 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 
     75 bool PanelStackWindowGtk::IsEmpty() const {
     76   return panels_.empty();
     77 }
     78 
     79 bool PanelStackWindowGtk::HasPanel(Panel* panel) const {
     80   return std::find(panels_.begin(), panels_.end(), panel) != panels_.end();
     81 }
     82 
     83 void 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 
     95 void PanelStackWindowGtk::BeginBatchUpdatePanelBounds(bool animate) {
     96   // Bounds animation is not supported on GTK.
     97   bounds_updates_started_ = true;
     98 }
     99 
    100 void 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 
    112 void 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 
    128 bool PanelStackWindowGtk::IsAnimatingPanelBounds() const {
    129   return bounds_updates_started_;
    130 }
    131 
    132 void PanelStackWindowGtk::Minimize() {
    133   gtk_window_iconify(window_);
    134 }
    135 
    136 bool PanelStackWindowGtk::IsMinimized() const {
    137   return is_minimized_;
    138 }
    139 
    140 void PanelStackWindowGtk::DrawSystemAttention(bool draw_attention) {
    141   gtk_window_set_urgency_hint(window_, draw_attention);
    142 }
    143 
    144 void 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 
    157 void 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 
    177 gboolean 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 
    193 gboolean 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 
    212 void 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 
    247 void 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