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