• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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:
TitleBackgroundPainter(PanelController * controller)66   explicit TitleBackgroundPainter(PanelController* controller)
67       : panel_controller_(controller) { }
68 
69  private:
Paint(int w,int h,gfx::Canvas * canvas)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;
InitializeResources()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 
PanelController(Delegate * delegate,GtkWindow * window)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 
Init(bool initial_focus,const gfx::Rect & window_bounds,XID creator_xid,WmIpcPanelUserResizeType resize_type)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 
UpdateTitleBar()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 
SetUrgent(bool urgent)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 
TitleMousePressed(const views::MouseEvent & event)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 
TitleMouseReleased(const views::MouseEvent & event)229 void PanelController::TitleMouseReleased(const views::MouseEvent& event) {
230   if (event.IsLeftMouseButton())
231     TitleMouseCaptureLost();
232 }
233 
TitleMouseCaptureLost()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 
SetState(State state)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 
TitleMouseDragged(const views::MouseEvent & event)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
OnPanelClientEvent(GtkWidget * widget,GdkEventClient * event,PanelController * panel_controller)299 bool PanelController::OnPanelClientEvent(
300     GtkWidget* widget,
301     GdkEventClient* event,
302     PanelController* panel_controller) {
303   return panel_controller->PanelClientEvent(event);
304 }
305 
OnFocusIn()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 
OnFocusOut()314 void PanelController::OnFocusOut() {
315   focused_ = false;
316   if (title_window_)
317     title_content_->OnFocusOut();
318 }
319 
PanelClientEvent(GdkEventClient * event)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 
Close()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 
OnCloseButtonPressed()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 
TitleContentView(PanelController * panel_controller)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 
Layout()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 
OnMousePressed(const views::MouseEvent & event)410 bool PanelController::TitleContentView::OnMousePressed(
411     const views::MouseEvent& event) {
412   return panel_controller_->TitleMousePressed(event);
413 }
414 
OnMouseReleased(const views::MouseEvent & event)415 void PanelController::TitleContentView::OnMouseReleased(
416     const views::MouseEvent& event) {
417   panel_controller_->TitleMouseReleased(event);
418 }
419 
OnMouseCaptureLost()420 void PanelController::TitleContentView::OnMouseCaptureLost() {
421   panel_controller_->TitleMouseCaptureLost();
422 }
423 
OnMouseDragged(const views::MouseEvent & event)424 bool PanelController::TitleContentView::OnMouseDragged(
425     const views::MouseEvent& event) {
426   return panel_controller_->TitleMouseDragged(event);
427 }
428 
OnFocusIn()429 void PanelController::TitleContentView::OnFocusIn() {
430   title_label_->SetColor(kTitleActiveColor);
431   title_label_->SetFont(*active_font);
432   Layout();
433   SchedulePaint();
434 }
435 
OnFocusOut()436 void PanelController::TitleContentView::OnFocusOut() {
437   title_label_->SetColor(kTitleInactiveColor);
438   title_label_->SetFont(*inactive_font);
439   Layout();
440   SchedulePaint();
441 }
442 
OnClose()443 void PanelController::TitleContentView::OnClose() {
444   panel_controller_ = NULL;
445 }
446 
ButtonPressed(views::Button * sender,const views::Event & event)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 
~TitleContentView()453 PanelController::TitleContentView::~TitleContentView() {
454   VLOG(1) << "panel: delete " << this;
455 }
456 
457 }  // namespace chromeos
458