Home | History | Annotate | Download | only in frame
      1 // Copyright (c) 2011 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/chromeos/frame/panel_controller.h"
      6 
      7 #include <vector>
      8 
      9 #include "base/logging.h"
     10 #include "base/memory/scoped_ptr.h"
     11 #include "base/memory/singleton.h"
     12 #include "base/string_util.h"
     13 #include "base/time.h"
     14 #include "base/utf_string_conversions.h"
     15 #include "chrome/browser/chromeos/wm_ipc.h"
     16 #include "content/common/notification_service.h"
     17 #include "grit/app_resources.h"
     18 #include "grit/generated_resources.h"
     19 #include "grit/theme_resources.h"
     20 #include "third_party/cros/chromeos_wm_ipc_enums.h"
     21 #include "third_party/skia/include/effects/SkBlurMaskFilter.h"
     22 #include "third_party/skia/include/effects/SkGradientShader.h"
     23 #include "ui/base/resource/resource_bundle.h"
     24 #include "ui/gfx/canvas_skia.h"
     25 #include "views/controls/button/image_button.h"
     26 #include "views/controls/image_view.h"
     27 #include "views/controls/label.h"
     28 #include "views/events/event.h"
     29 #include "views/painter.h"
     30 #include "views/view.h"
     31 #include "views/widget/widget.h"
     32 #include "views/window/window.h"
     33 
     34 namespace chromeos {
     35 
     36 static int close_button_width;
     37 static int close_button_height;
     38 static SkBitmap* close_button_n;
     39 static SkBitmap* close_button_m;
     40 static SkBitmap* close_button_h;
     41 static SkBitmap* close_button_p;
     42 static gfx::Font* active_font = NULL;
     43 static gfx::Font* inactive_font = NULL;
     44 
     45 namespace {
     46 
     47 const int kTitleHeight = 24;
     48 const int kTitleIconSize = 16;
     49 const int kTitleWidthPad = 4;
     50 const int kTitleHeightPad = 4;
     51 const int kTitleCornerRadius = 4;
     52 const int kTitleCloseButtonPad = 6;
     53 const SkColor kTitleActiveGradientStart = SK_ColorWHITE;
     54 const SkColor kTitleActiveGradientEnd = 0xffe7edf1;
     55 const SkColor kTitleUrgentGradientStart = 0xfffea044;
     56 const SkColor kTitleUrgentGradientEnd = 0xfffa983a;
     57 const SkColor kTitleActiveColor = SK_ColorBLACK;
     58 const SkColor kTitleInactiveColor = SK_ColorBLACK;
     59 const SkColor kTitleCloseButtonColor = SK_ColorBLACK;
     60 // Delay before the urgency can be set after it has been cleared.
     61 const base::TimeDelta kSetUrgentDelay = base::TimeDelta::FromMilliseconds(500);
     62 
     63 // Used to draw the background of the panel title window.
     64 class TitleBackgroundPainter : public views::Painter {
     65  public:
     66   explicit TitleBackgroundPainter(PanelController* controller)
     67       : panel_controller_(controller) { }
     68 
     69  private:
     70   virtual void Paint(int w, int h, gfx::Canvas* canvas) {
     71     SkRect rect = {0, 0, w, h};
     72     SkPath path;
     73     SkScalar corners[] = {
     74         kTitleCornerRadius, kTitleCornerRadius,
     75         kTitleCornerRadius, kTitleCornerRadius,
     76         0, 0,
     77         0, 0
     78     };
     79     path.addRoundRect(rect, corners);
     80     SkPaint paint;
     81     paint.setStyle(SkPaint::kFill_Style);
     82     paint.setFlags(SkPaint::kAntiAlias_Flag);
     83     SkPoint p[2] = { {0, 0}, {0, h} };
     84     SkColor colors[2] = {kTitleActiveGradientStart, kTitleActiveGradientEnd};
     85     if (panel_controller_->urgent()) {
     86       colors[0] = kTitleUrgentGradientStart;
     87       colors[1] = kTitleUrgentGradientEnd;
     88     }
     89     SkShader* s = SkGradientShader::CreateLinear(
     90         p, colors, NULL, 2, SkShader::kClamp_TileMode, NULL);
     91     paint.setShader(s);
     92     // Need to unref shader, otherwise never deleted.
     93     s->unref();
     94     canvas->AsCanvasSkia()->drawPath(path, paint);
     95   }
     96 
     97   PanelController* panel_controller_;
     98 };
     99 
    100 static bool resources_initialized;
    101 static void InitializeResources() {
    102   if (resources_initialized) {
    103     return;
    104   }
    105 
    106   resources_initialized = true;
    107   ResourceBundle& rb = ResourceBundle::GetSharedInstance();
    108   gfx::Font base_font = rb.GetFont(ResourceBundle::BaseFont);
    109   // Title fonts are the same for active and inactive.
    110   inactive_font = new gfx::Font(base_font.DeriveFont(0, gfx::Font::BOLD));
    111   active_font = inactive_font;
    112   close_button_n = rb.GetBitmapNamed(IDR_TAB_CLOSE);
    113   close_button_m = rb.GetBitmapNamed(IDR_TAB_CLOSE_MASK);
    114   close_button_h = rb.GetBitmapNamed(IDR_TAB_CLOSE_H);
    115   close_button_p = rb.GetBitmapNamed(IDR_TAB_CLOSE_P);
    116   close_button_width = close_button_n->width();
    117   close_button_height = close_button_n->height();
    118 }
    119 
    120 }  // namespace
    121 
    122 PanelController::PanelController(Delegate* delegate,
    123                                  GtkWindow* window)
    124     :  delegate_(delegate),
    125        panel_(window),
    126        panel_xid_(ui::GetX11WindowFromGtkWidget(GTK_WIDGET(panel_))),
    127        title_window_(NULL),
    128        title_(NULL),
    129        title_content_(NULL),
    130        expanded_(true),
    131        mouse_down_(false),
    132        dragging_(false),
    133        client_event_handler_id_(0),
    134        focused_(false),
    135        urgent_(false) {
    136 }
    137 
    138 void PanelController::Init(bool initial_focus,
    139                            const gfx::Rect& window_bounds,
    140                            XID creator_xid,
    141                            WmIpcPanelUserResizeType resize_type) {
    142   gfx::Rect title_bounds(0, 0, window_bounds.width(), kTitleHeight);
    143 
    144   views::Widget::CreateParams params(views::Widget::CreateParams::TYPE_WINDOW);
    145   params.transparent = true;
    146   title_window_ = views::Widget::CreateWidget(params);
    147   title_window_->Init(NULL, title_bounds);
    148   gtk_widget_set_size_request(title_window_->GetNativeView(),
    149                               title_bounds.width(), title_bounds.height());
    150   title_ = title_window_->GetNativeView();
    151   title_xid_ = ui::GetX11WindowFromGtkWidget(title_);
    152 
    153   WmIpc::instance()->SetWindowType(
    154       title_,
    155       WM_IPC_WINDOW_CHROME_PANEL_TITLEBAR,
    156       NULL);
    157   std::vector<int> type_params;
    158   type_params.push_back(title_xid_);
    159   type_params.push_back(expanded_ ? 1 : 0);
    160   type_params.push_back(initial_focus ? 1 : 0);
    161   type_params.push_back(creator_xid);
    162   type_params.push_back(resize_type);
    163   WmIpc::instance()->SetWindowType(
    164       GTK_WIDGET(panel_),
    165       WM_IPC_WINDOW_CHROME_PANEL_CONTENT,
    166       &type_params);
    167 
    168   client_event_handler_id_ = g_signal_connect(
    169       panel_, "client-event", G_CALLBACK(OnPanelClientEvent), this);
    170 
    171   title_content_ = new TitleContentView(this);
    172   title_window_->SetContentsView(title_content_);
    173   UpdateTitleBar();
    174   title_window_->Show();
    175 }
    176 
    177 void PanelController::UpdateTitleBar() {
    178   if (!delegate_ || !title_window_)
    179     return;
    180   title_content_->title_label()->SetText(
    181       UTF16ToWideHack(delegate_->GetPanelTitle()));
    182   title_content_->title_icon()->SetImage(delegate_->GetPanelIcon());
    183 }
    184 
    185 void PanelController::SetUrgent(bool urgent) {
    186   if (!urgent)
    187     urgent_cleared_time_ = base::TimeTicks::Now();
    188   if (urgent == urgent_)
    189     return;
    190   if (urgent && focused_)
    191     return;  // Don't set urgency for focused panels.
    192   if (urgent && base::TimeTicks::Now() < urgent_cleared_time_ + kSetUrgentDelay)
    193     return;  // Don't set urgency immediately after clearing it.
    194   urgent_ = urgent;
    195   if (title_window_) {
    196     gtk_window_set_urgency_hint(panel_, urgent ? TRUE : FALSE);
    197     title_content_->SchedulePaint();
    198   }
    199 }
    200 
    201 bool PanelController::TitleMousePressed(const views::MouseEvent& event) {
    202   if (!event.IsOnlyLeftMouseButton())
    203     return false;
    204   GdkEvent* gdk_event = gtk_get_current_event();
    205   if (gdk_event->type != GDK_BUTTON_PRESS) {
    206     gdk_event_free(gdk_event);
    207     NOTREACHED();
    208     return false;
    209   }
    210   DCHECK(title_);
    211   // Get the last titlebar width that we saw in a ConfigureNotify event -- we
    212   // need to give drag positions in terms of the top-right corner of the
    213   // titlebar window.  See WM_IPC_MESSAGE_WM_NOTIFY_PANEL_DRAGGED's declaration
    214   // for details.
    215   gint title_width = 1;
    216   gtk_window_get_size(GTK_WINDOW(title_), &title_width, NULL);
    217 
    218   GdkEventButton last_button_event = gdk_event->button;
    219   mouse_down_ = true;
    220   mouse_down_abs_x_ = last_button_event.x_root;
    221   mouse_down_abs_y_ = last_button_event.y_root;
    222   mouse_down_offset_x_ = event.x() - title_width;
    223   mouse_down_offset_y_ = event.y();
    224   dragging_ = false;
    225   gdk_event_free(gdk_event);
    226   return true;
    227 }
    228 
    229 void PanelController::TitleMouseReleased(const views::MouseEvent& event) {
    230   if (event.IsLeftMouseButton())
    231     TitleMouseCaptureLost();
    232 }
    233 
    234 void PanelController::TitleMouseCaptureLost() {
    235   // Only handle clicks that started in our window.
    236   if (!mouse_down_)
    237     return;
    238 
    239   mouse_down_ = false;
    240   if (!dragging_) {
    241     if (expanded_) {
    242       // Always activate the panel here, even if we are about to minimize it.
    243       // This lets panels like GTalk know that they have been acknowledged, so
    244       // they don't change the title again (which would trigger SetUrgent).
    245       // Activating the panel also clears the urgent state.
    246       delegate_->ActivatePanel();
    247       SetState(PanelController::MINIMIZED);
    248     } else {
    249       // If we're expanding the panel, do so before focusing it.  This lets the
    250       // window manager know that the panel is being expanded in response to a
    251       // user action; see http://crosbug.com/14735.
    252       SetState(PanelController::EXPANDED);
    253       delegate_->ActivatePanel();
    254     }
    255   } else {
    256     WmIpc::Message msg(WM_IPC_MESSAGE_WM_NOTIFY_PANEL_DRAG_COMPLETE);
    257     msg.set_param(0, panel_xid_);
    258     WmIpc::instance()->SendMessage(msg);
    259     dragging_ = false;
    260   }
    261 }
    262 
    263 void PanelController::SetState(State state) {
    264   WmIpc::Message msg(WM_IPC_MESSAGE_WM_SET_PANEL_STATE);
    265   msg.set_param(0, panel_xid_);
    266   msg.set_param(1, state == EXPANDED);
    267   WmIpc::instance()->SendMessage(msg);
    268 }
    269 
    270 bool PanelController::TitleMouseDragged(const views::MouseEvent& event) {
    271   if (!mouse_down_)
    272     return false;
    273   GdkEvent* gdk_event = gtk_get_current_event();
    274   if (gdk_event->type != GDK_MOTION_NOTIFY) {
    275     gdk_event_free(gdk_event);
    276     NOTREACHED();
    277     return false;
    278   }
    279   GdkEventMotion last_motion_event = gdk_event->motion;
    280   if (!dragging_) {
    281     if (views::View::ExceededDragThreshold(
    282         last_motion_event.x_root - mouse_down_abs_x_,
    283         last_motion_event.y_root - mouse_down_abs_y_)) {
    284       dragging_ = true;
    285     }
    286   }
    287   if (dragging_) {
    288     WmIpc::Message msg(WM_IPC_MESSAGE_WM_NOTIFY_PANEL_DRAGGED);
    289     msg.set_param(0, panel_xid_);
    290     msg.set_param(1, last_motion_event.x_root - mouse_down_offset_x_);
    291     msg.set_param(2, last_motion_event.y_root - mouse_down_offset_y_);
    292     WmIpc::instance()->SendMessage(msg);
    293   }
    294   gdk_event_free(gdk_event);
    295   return true;
    296 }
    297 
    298 // static
    299 bool PanelController::OnPanelClientEvent(
    300     GtkWidget* widget,
    301     GdkEventClient* event,
    302     PanelController* panel_controller) {
    303   return panel_controller->PanelClientEvent(event);
    304 }
    305 
    306 void PanelController::OnFocusIn() {
    307   if (title_window_)
    308     title_content_->OnFocusIn();
    309   focused_ = true;
    310   // Clear urgent when focused.
    311   SetUrgent(false);
    312 }
    313 
    314 void PanelController::OnFocusOut() {
    315   focused_ = false;
    316   if (title_window_)
    317     title_content_->OnFocusOut();
    318 }
    319 
    320 bool PanelController::PanelClientEvent(GdkEventClient* event) {
    321   WmIpc::Message msg;
    322   WmIpc::instance()->DecodeMessage(*event, &msg);
    323   if (msg.type() == WM_IPC_MESSAGE_CHROME_NOTIFY_PANEL_STATE) {
    324     bool new_state = msg.param(0);
    325     if (expanded_ != new_state) {
    326       expanded_ = new_state;
    327       State state = new_state ? EXPANDED : MINIMIZED;
    328       NotificationService::current()->Notify(
    329           NotificationType::PANEL_STATE_CHANGED,
    330           Source<PanelController>(this),
    331           Details<State>(&state));
    332     }
    333   }
    334   return true;
    335 }
    336 
    337 void PanelController::Close() {
    338   if (client_event_handler_id_ > 0) {
    339     g_signal_handler_disconnect(panel_, client_event_handler_id_);
    340     client_event_handler_id_ = 0;
    341   }
    342   // ignore if the title window is already closed.
    343   if (title_window_) {
    344     title_window_->Close();
    345     title_window_ = NULL;
    346     title_ = NULL;
    347     title_content_->OnClose();
    348     title_content_ = NULL;
    349   }
    350 }
    351 
    352 void PanelController::OnCloseButtonPressed() {
    353   DCHECK(title_content_);
    354   if (title_window_) {
    355     if (delegate_) {
    356       if (!delegate_->CanClosePanel())
    357         return;
    358       delegate_->ClosePanel();
    359     }
    360     Close();
    361   }
    362 }
    363 
    364 PanelController::TitleContentView::TitleContentView(
    365     PanelController* panel_controller)
    366         : panel_controller_(panel_controller) {
    367   VLOG(1) << "panel: c " << this;
    368   InitializeResources();
    369   close_button_ = new views::ImageButton(this);
    370   close_button_->SetImage(views::CustomButton::BS_NORMAL, close_button_n);
    371   close_button_->SetImage(views::CustomButton::BS_HOT, close_button_h);
    372   close_button_->SetImage(views::CustomButton::BS_PUSHED, close_button_p);
    373   close_button_->SetBackground(
    374       kTitleCloseButtonColor, close_button_n, close_button_m);
    375   AddChildView(close_button_);
    376 
    377   title_icon_ = new views::ImageView();
    378   AddChildView(title_icon_);
    379   title_label_ = new views::Label(std::wstring());
    380   title_label_->SetHorizontalAlignment(views::Label::ALIGN_LEFT);
    381   AddChildView(title_label_);
    382 
    383   set_background(
    384       views::Background::CreateBackgroundPainter(
    385           true, new TitleBackgroundPainter(panel_controller)));
    386   OnFocusOut();
    387 }
    388 
    389 void PanelController::TitleContentView::Layout() {
    390   int close_button_x = bounds().width() -
    391       (close_button_width + kTitleCloseButtonPad);
    392   close_button_->SetBounds(
    393       close_button_x,
    394       (bounds().height() - close_button_height) / 2,
    395       close_button_width,
    396       close_button_height);
    397   title_icon_->SetBounds(
    398       kTitleWidthPad,
    399       kTitleHeightPad,
    400       kTitleIconSize,
    401       kTitleIconSize);
    402   int title_x = kTitleWidthPad * 2 + kTitleIconSize;
    403   title_label_->SetBounds(
    404       title_x,
    405       0,
    406       close_button_x - (title_x + kTitleCloseButtonPad),
    407       bounds().height());
    408 }
    409 
    410 bool PanelController::TitleContentView::OnMousePressed(
    411     const views::MouseEvent& event) {
    412   return panel_controller_->TitleMousePressed(event);
    413 }
    414 
    415 void PanelController::TitleContentView::OnMouseReleased(
    416     const views::MouseEvent& event) {
    417   panel_controller_->TitleMouseReleased(event);
    418 }
    419 
    420 void PanelController::TitleContentView::OnMouseCaptureLost() {
    421   panel_controller_->TitleMouseCaptureLost();
    422 }
    423 
    424 bool PanelController::TitleContentView::OnMouseDragged(
    425     const views::MouseEvent& event) {
    426   return panel_controller_->TitleMouseDragged(event);
    427 }
    428 
    429 void PanelController::TitleContentView::OnFocusIn() {
    430   title_label_->SetColor(kTitleActiveColor);
    431   title_label_->SetFont(*active_font);
    432   Layout();
    433   SchedulePaint();
    434 }
    435 
    436 void PanelController::TitleContentView::OnFocusOut() {
    437   title_label_->SetColor(kTitleInactiveColor);
    438   title_label_->SetFont(*inactive_font);
    439   Layout();
    440   SchedulePaint();
    441 }
    442 
    443 void PanelController::TitleContentView::OnClose() {
    444   panel_controller_ = NULL;
    445 }
    446 
    447 void PanelController::TitleContentView::ButtonPressed(
    448     views::Button* sender, const views::Event& event) {
    449   if (panel_controller_ && sender == close_button_)
    450     panel_controller_->OnCloseButtonPressed();
    451 }
    452 
    453 PanelController::TitleContentView::~TitleContentView() {
    454   VLOG(1) << "panel: delete " << this;
    455 }
    456 
    457 }  // namespace chromeos
    458