| 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 |