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