panel_controller.cc revision 3345a6884c488ff3a535c2c9acdd33d74b37e311
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/browser.h" 16#include "chrome/browser/chromeos/wm_ipc.h" 17#include "chrome/common/notification_service.h" 18#include "cros/chromeos_wm_ipc_enums.h" 19#include "gfx/canvas_skia.h" 20#include "grit/app_resources.h" 21#include "grit/generated_resources.h" 22#include "grit/theme_resources.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 LOG(INFO) << "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 LOG(INFO) << "panel: delete " << this; 405} 406 407} // namespace chromeos 408