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/ui/gtk/panels/panel_gtk.h" | |
6 | |
7 #include <gdk/gdk.h> | |
8 #include <gdk/gdkkeysyms.h> | |
9 #include <X11/XF86keysym.h> | |
10 | |
11 #include "base/bind.h" | |
12 #include "base/debug/trace_event.h" | |
13 #include "base/logging.h" | |
14 #include "base/message_loop/message_loop.h" | |
15 #include "base/strings/utf_string_conversions.h" | |
16 #include "chrome/app/chrome_command_ids.h" | |
17 #include "chrome/browser/chrome_notification_types.h" | |
18 #include "chrome/browser/ui/app_modal_dialogs/app_modal_dialog_queue.h" | |
19 #include "chrome/browser/ui/gtk/custom_button.h" | |
20 #include "chrome/browser/ui/gtk/gtk_util.h" | |
21 #include "chrome/browser/ui/gtk/gtk_window_util.h" | |
22 #include "chrome/browser/ui/gtk/panels/panel_drag_gtk.h" | |
23 #include "chrome/browser/ui/gtk/panels/panel_titlebar_gtk.h" | |
24 #include "chrome/browser/ui/panels/display_settings_provider.h" | |
25 #include "chrome/browser/ui/panels/panel.h" | |
26 #include "chrome/browser/ui/panels/panel_constants.h" | |
27 #include "chrome/browser/ui/panels/panel_manager.h" | |
28 #include "chrome/browser/ui/panels/stacked_panel_collection.h" | |
29 #include "chrome/browser/web_applications/web_app.h" | |
30 #include "content/public/browser/native_web_keyboard_event.h" | |
31 #include "content/public/browser/notification_service.h" | |
32 #include "content/public/browser/web_contents.h" | |
33 #include "content/public/browser/web_contents_view.h" | |
34 #include "grit/ui_resources.h" | |
35 #include "ui/base/accelerators/platform_accelerator_gtk.h" | |
36 #include "ui/base/gtk/gtk_expanded_container.h" | |
37 #include "ui/base/gtk/gtk_hig_constants.h" | |
38 #include "ui/base/x/active_window_watcher_x.h" | |
39 #include "ui/base/x/x11_util.h" | |
40 #include "ui/gfx/canvas.h" | |
41 #include "ui/gfx/gtk_compat.h" | |
42 #include "ui/gfx/image/cairo_cached_surface.h" | |
43 #include "ui/gfx/image/image.h" | |
44 | |
45 using content::NativeWebKeyboardEvent; | |
46 using content::WebContents; | |
47 | |
48 namespace { | |
49 | |
50 const char* kPanelWindowKey = "__PANEL_GTK__"; | |
51 | |
52 // The number of milliseconds between loading animation frames. | |
53 const int kLoadingAnimationFrameTimeMs = 30; | |
54 | |
55 // The frame border is only visible in restored mode and is hardcoded to 4 px | |
56 // on each side regardless of the system window border size. | |
57 const int kFrameBorderThickness = 4; | |
58 // While resize areas on Windows are normally the same size as the window | |
59 // borders, our top area is shrunk by 1 px to make it easier to move the window | |
60 // around with our thinner top grabbable strip. (Incidentally, our side and | |
61 // bottom resize areas don't match the frame border thickness either -- they | |
62 // span the whole nonclient area, so there's no "dead zone" for the mouse.) | |
63 const int kTopResizeAdjust = 1; | |
64 // In the window corners, the resize areas don't actually expand bigger, but | |
65 // the 16 px at the end of each edge triggers diagonal resizing. | |
66 const int kResizeAreaCornerSize = 16; | |
67 | |
68 // Colors used to draw frame background under default theme. | |
69 const SkColor kActiveBackgroundDefaultColor = SkColorSetRGB(0x3a, 0x3d, 0x3d); | |
70 const SkColor kInactiveBackgroundDefaultColor = SkColorSetRGB(0x7a, 0x7c, 0x7c); | |
71 const SkColor kAttentionBackgroundDefaultColor = | |
72 SkColorSetRGB(0x53, 0xa9, 0x3f); | |
73 const SkColor kMinimizeBackgroundDefaultColor = SkColorSetRGB(0xf5, 0xf4, 0xf0); | |
74 const SkColor kMinimizeBorderDefaultColor = SkColorSetRGB(0xc9, 0xc9, 0xc9); | |
75 | |
76 // Set minimium width for window really small. | |
77 const int kMinWindowWidth = 26; | |
78 | |
79 // Table of supported accelerators in Panels. | |
80 const struct AcceleratorMapping { | |
81 guint keyval; | |
82 int command_id; | |
83 GdkModifierType modifier_type; | |
84 } kAcceleratorMap[] = { | |
85 // Window controls. | |
86 { GDK_w, IDC_CLOSE_WINDOW, GDK_CONTROL_MASK }, | |
87 { GDK_w, IDC_CLOSE_WINDOW, | |
88 GdkModifierType(GDK_CONTROL_MASK | GDK_SHIFT_MASK) }, | |
89 { GDK_q, IDC_EXIT, GdkModifierType(GDK_CONTROL_MASK | GDK_SHIFT_MASK) }, | |
90 | |
91 // Zoom level. | |
92 { GDK_KP_Add, IDC_ZOOM_PLUS, GDK_CONTROL_MASK }, | |
93 { GDK_plus, IDC_ZOOM_PLUS, | |
94 GdkModifierType(GDK_CONTROL_MASK | GDK_SHIFT_MASK) }, | |
95 { GDK_equal, IDC_ZOOM_PLUS, GDK_CONTROL_MASK }, | |
96 { XF86XK_ZoomIn, IDC_ZOOM_PLUS, GdkModifierType(0) }, | |
97 { GDK_KP_0, IDC_ZOOM_NORMAL, GDK_CONTROL_MASK }, | |
98 { GDK_0, IDC_ZOOM_NORMAL, GDK_CONTROL_MASK }, | |
99 { GDK_KP_Subtract, IDC_ZOOM_MINUS, GDK_CONTROL_MASK }, | |
100 { GDK_minus, IDC_ZOOM_MINUS, GDK_CONTROL_MASK }, | |
101 { GDK_underscore, IDC_ZOOM_MINUS, | |
102 GdkModifierType(GDK_CONTROL_MASK | GDK_SHIFT_MASK) }, | |
103 { XF86XK_ZoomOut, IDC_ZOOM_MINUS, GdkModifierType(0) }, | |
104 | |
105 // Navigation. | |
106 { GDK_Escape, IDC_STOP, GdkModifierType(0) }, | |
107 { XF86XK_Stop, IDC_STOP, GdkModifierType(0) }, | |
108 { GDK_r, IDC_RELOAD, GDK_CONTROL_MASK }, | |
109 { GDK_r, IDC_RELOAD_IGNORING_CACHE, | |
110 GdkModifierType(GDK_CONTROL_MASK|GDK_SHIFT_MASK) }, | |
111 { GDK_F5, IDC_RELOAD, GdkModifierType(0) }, | |
112 { GDK_F5, IDC_RELOAD_IGNORING_CACHE, GDK_CONTROL_MASK }, | |
113 { GDK_F5, IDC_RELOAD_IGNORING_CACHE, GDK_SHIFT_MASK }, | |
114 { XF86XK_Reload, IDC_RELOAD, GdkModifierType(0) }, | |
115 { XF86XK_Refresh, IDC_RELOAD, GdkModifierType(0) }, | |
116 | |
117 // Editing. | |
118 { GDK_c, IDC_COPY, GDK_CONTROL_MASK }, | |
119 { GDK_x, IDC_CUT, GDK_CONTROL_MASK }, | |
120 { GDK_v, IDC_PASTE, GDK_CONTROL_MASK }, | |
121 | |
122 // Dev tools. | |
123 { GDK_i, IDC_DEV_TOOLS, | |
124 GdkModifierType(GDK_CONTROL_MASK | GDK_SHIFT_MASK) }, | |
125 { GDK_j, IDC_DEV_TOOLS_CONSOLE, | |
126 GdkModifierType(GDK_CONTROL_MASK | GDK_SHIFT_MASK) }, | |
127 | |
128 }; | |
129 | |
130 // Table of accelerator mappings to command ids. | |
131 typedef std::map<ui::Accelerator, int> AcceleratorMap; | |
132 | |
133 const AcceleratorMap& GetAcceleratorTable() { | |
134 CR_DEFINE_STATIC_LOCAL(AcceleratorMap, accelerator_table, ()); | |
135 if (accelerator_table.empty()) { | |
136 for (size_t i = 0; i < arraysize(kAcceleratorMap); ++i) { | |
137 const AcceleratorMapping& entry = kAcceleratorMap[i]; | |
138 ui::Accelerator accelerator = ui::AcceleratorForGdkKeyCodeAndModifier( | |
139 entry.keyval, entry.modifier_type); | |
140 accelerator_table[accelerator] = entry.command_id; | |
141 } | |
142 } | |
143 return accelerator_table; | |
144 } | |
145 | |
146 gfx::Image CreateImageForColor(SkColor color) { | |
147 gfx::Canvas canvas(gfx::Size(1, 1), 1.0f, true); | |
148 canvas.DrawColor(color); | |
149 return gfx::Image(gfx::ImageSkia(canvas.ExtractImageRep())); | |
150 } | |
151 | |
152 const gfx::Image GetActiveBackgroundDefaultImage() { | |
153 CR_DEFINE_STATIC_LOCAL(gfx::Image, image, ()); | |
154 if (image.IsEmpty()) | |
155 image = CreateImageForColor(kActiveBackgroundDefaultColor); | |
156 return image; | |
157 } | |
158 | |
159 gfx::Image GetInactiveBackgroundDefaultImage() { | |
160 CR_DEFINE_STATIC_LOCAL(gfx::Image, image, ()); | |
161 if (image.IsEmpty()) | |
162 image = CreateImageForColor(kInactiveBackgroundDefaultColor); | |
163 return image; | |
164 } | |
165 | |
166 gfx::Image GetAttentionBackgroundDefaultImage() { | |
167 CR_DEFINE_STATIC_LOCAL(gfx::Image, image, ()); | |
168 if (image.IsEmpty()) | |
169 image = CreateImageForColor(kAttentionBackgroundDefaultColor); | |
170 return image; | |
171 } | |
172 | |
173 gfx::Image GetMinimizeBackgroundDefaultImage() { | |
174 CR_DEFINE_STATIC_LOCAL(gfx::Image, image, ()); | |
175 if (image.IsEmpty()) | |
176 image = CreateImageForColor(kMinimizeBackgroundDefaultColor); | |
177 return image; | |
178 } | |
179 | |
180 // Used to stash a pointer to the Panel window inside the native | |
181 // Gtk window for retrieval in static callbacks. | |
182 GQuark GetPanelWindowQuarkKey() { | |
183 static GQuark quark = g_quark_from_static_string(kPanelWindowKey); | |
184 return quark; | |
185 } | |
186 | |
187 // Size of window frame. Empty until first panel has been allocated | |
188 // and sized. Frame size won't change for other panels so it can be | |
189 // computed once for all panels. | |
190 gfx::Size& GetFrameSize() { | |
191 CR_DEFINE_STATIC_LOCAL(gfx::Size, frame_size, ()); | |
192 return frame_size; | |
193 } | |
194 | |
195 void SetFrameSize(const gfx::Size& new_size) { | |
196 gfx::Size& frame_size = GetFrameSize(); | |
197 frame_size.SetSize(new_size.width(), new_size.height()); | |
198 } | |
199 | |
200 } // namespace | |
201 | |
202 // static | |
203 NativePanel* Panel::CreateNativePanel(Panel* panel, | |
204 const gfx::Rect& bounds, | |
205 bool always_on_top) { | |
206 PanelGtk* panel_gtk = new PanelGtk(panel, bounds, always_on_top); | |
207 panel_gtk->Init(); | |
208 return panel_gtk; | |
209 } | |
210 | |
211 PanelGtk::PanelGtk(Panel* panel, const gfx::Rect& bounds, bool always_on_top) | |
212 : panel_(panel), | |
213 bounds_(bounds), | |
214 always_on_top_(always_on_top), | |
215 is_shown_(false), | |
216 paint_state_(PAINT_AS_INACTIVE), | |
217 is_drawing_attention_(false), | |
218 frame_cursor_(NULL), | |
219 is_active_(!ui::ActiveWindowWatcherX::WMSupportsActivation()), | |
220 is_minimized_(false), | |
221 window_(NULL), | |
222 window_container_(NULL), | |
223 window_vbox_(NULL), | |
224 render_area_event_box_(NULL), | |
225 contents_expanded_(NULL), | |
226 accel_group_(NULL), | |
227 corner_style_(panel::ALL_ROUNDED) { | |
228 } | |
229 | |
230 PanelGtk::~PanelGtk() { | |
231 ui::ActiveWindowWatcherX::RemoveObserver(this); | |
232 } | |
233 | |
234 void PanelGtk::Init() { | |
235 ui::ActiveWindowWatcherX::AddObserver(this); | |
236 | |
237 window_ = GTK_WINDOW(gtk_window_new(GTK_WINDOW_TOPLEVEL)); | |
238 g_object_set_qdata(G_OBJECT(window_), GetPanelWindowQuarkKey(), this); | |
239 gtk_widget_add_events(GTK_WIDGET(window_), GDK_BUTTON_PRESS_MASK | | |
240 GDK_POINTER_MOTION_MASK); | |
241 gtk_window_set_decorated(window_, false); | |
242 | |
243 // Disable the resize gripper on Ubuntu. | |
244 gtk_window_util::DisableResizeGrip(window_); | |
245 | |
246 // Add this window to its own unique window group to allow for | |
247 // window-to-parent modality. | |
248 gtk_window_group_add_window(gtk_window_group_new(), window_); | |
249 g_object_unref(gtk_window_get_group(window_)); | |
250 | |
251 // Set minimum height for the window. | |
252 GdkGeometry hints; | |
253 hints.min_height = panel::kMinimizedPanelHeight; | |
254 hints.min_width = kMinWindowWidth; | |
255 gtk_window_set_geometry_hints( | |
256 window_, GTK_WIDGET(window_), &hints, GDK_HINT_MIN_SIZE); | |
257 | |
258 // Connect signal handlers to the window. | |
259 g_signal_connect(window_, "delete-event", | |
260 G_CALLBACK(OnMainWindowDeleteEventThunk), this); | |
261 g_signal_connect(window_, "destroy", | |
262 G_CALLBACK(OnMainWindowDestroyThunk), this); | |
263 g_signal_connect(window_, "configure-event", | |
264 G_CALLBACK(OnConfigureThunk), this); | |
265 g_signal_connect(window_, "window-state-event", | |
266 G_CALLBACK(OnWindowStateThunk), this); | |
267 g_signal_connect(window_, "key-press-event", | |
268 G_CALLBACK(OnKeyPressThunk), this); | |
269 g_signal_connect(window_, "motion-notify-event", | |
270 G_CALLBACK(OnMouseMoveEventThunk), this); | |
271 g_signal_connect(window_, "button-press-event", | |
272 G_CALLBACK(OnButtonPressEventThunk), this); | |
273 | |
274 // This vbox contains the titlebar and the render area, but not | |
275 // the custom frame border. | |
276 window_vbox_ = gtk_vbox_new(FALSE, 0); | |
277 gtk_widget_show(window_vbox_); | |
278 | |
279 // TODO(jennb): add GlobalMenuBar after refactoring out Browser. | |
280 | |
281 // The window container draws the custom window frame. | |
282 window_container_ = gtk_alignment_new(0.0, 0.0, 1.0, 1.0); | |
283 gtk_widget_set_name(window_container_, "chrome-custom-frame-border"); | |
284 gtk_widget_set_app_paintable(window_container_, TRUE); | |
285 gtk_widget_set_double_buffered(window_container_, FALSE); | |
286 gtk_widget_set_redraw_on_allocate(window_container_, TRUE); | |
287 gtk_alignment_set_padding(GTK_ALIGNMENT(window_container_), 0, | |
288 kFrameBorderThickness, kFrameBorderThickness, kFrameBorderThickness); | |
289 g_signal_connect(window_container_, "expose-event", | |
290 G_CALLBACK(OnCustomFrameExposeThunk), this); | |
291 gtk_container_add(GTK_CONTAINER(window_container_), window_vbox_); | |
292 | |
293 // Build the titlebar. | |
294 titlebar_.reset(new PanelTitlebarGtk(this)); | |
295 titlebar_->Init(); | |
296 gtk_box_pack_start(GTK_BOX(window_vbox_), titlebar_->widget(), FALSE, FALSE, | |
297 0); | |
298 g_signal_connect(titlebar_->widget(), "button-press-event", | |
299 G_CALLBACK(OnTitlebarButtonPressEventThunk), this); | |
300 g_signal_connect(titlebar_->widget(), "button-release-event", | |
301 G_CALLBACK(OnTitlebarButtonReleaseEventThunk), this); | |
302 | |
303 contents_expanded_ = gtk_expanded_container_new(); | |
304 gtk_widget_show(contents_expanded_); | |
305 | |
306 render_area_event_box_ = gtk_event_box_new(); | |
307 // Set a white background so during startup the user sees white in the | |
308 // content area before we get a WebContents in place. | |
309 gtk_widget_modify_bg(render_area_event_box_, GTK_STATE_NORMAL, | |
310 &ui::kGdkWhite); | |
311 gtk_container_add(GTK_CONTAINER(render_area_event_box_), | |
312 contents_expanded_); | |
313 gtk_widget_show(render_area_event_box_); | |
314 gtk_box_pack_end(GTK_BOX(window_vbox_), render_area_event_box_, | |
315 TRUE, TRUE, 0); | |
316 | |
317 gtk_container_add(GTK_CONTAINER(window_), window_container_); | |
318 gtk_widget_show(window_container_); | |
319 | |
320 ConnectAccelerators(); | |
321 SetPanelAlwaysOnTop(always_on_top_); | |
322 } | |
323 | |
324 void PanelGtk::SetWindowCornerStyle(panel::CornerStyle corner_style) { | |
325 corner_style_ = corner_style; | |
326 UpdateWindowShape(); | |
327 } | |
328 | |
329 void PanelGtk::MinimizePanelBySystem() { | |
330 gtk_window_iconify(window_); | |
331 } | |
332 | |
333 bool PanelGtk::IsPanelMinimizedBySystem() const { | |
334 return is_minimized_; | |
335 } | |
336 | |
337 bool PanelGtk::IsPanelShownOnActiveDesktop() const { | |
338 // IsWindowVisible checks _NET_WM_DESKTOP. | |
339 if (!ui::IsWindowVisible(ui::GetX11WindowFromGtkWidget(GTK_WIDGET(window_)))) | |
340 return false; | |
341 | |
342 // Certain window manager, like Unity, does not update _NET_WM_DESKTOP when a | |
343 // window is moved to other workspace. However, it treats all workspaces as | |
344 // concatenated together in one big coordinate space. When the user switches | |
345 // to another workspace, the window manager will update the origins of all | |
346 // windows in previous active workspace to move by the size of display | |
347 // area. | |
348 gfx::Rect display_area = PanelManager::GetInstance()-> | |
349 display_settings_provider()->GetDisplayAreaMatching(bounds_); | |
350 int win_x = 0, win_y = 0; | |
351 gdk_window_get_origin(gtk_widget_get_window(GTK_WIDGET(window_)), | |
352 &win_x, &win_y); | |
353 return abs(win_x - bounds_.x()) < display_area.width() && | |
354 abs(win_y - bounds_.y()) < display_area.height(); | |
355 } | |
356 | |
357 void PanelGtk::ShowShadow(bool show) { | |
358 // Shadow is not supported for GTK panel. | |
359 } | |
360 | |
361 void PanelGtk::UpdateWindowShape() { | |
362 int width = configure_size_.width(); | |
363 int height = configure_size_.height(); | |
364 if (!width || !height) | |
365 return; | |
366 | |
367 GdkRegion* mask; | |
368 if (corner_style_ & panel::TOP_ROUNDED) { | |
369 GdkRectangle top_top_rect = { 3, 0, width - 6, 1 }; | |
370 GdkRectangle top_mid_rect = { 1, 1, width - 2, 2 }; | |
371 mask = gdk_region_rectangle(&top_top_rect); | |
372 gdk_region_union_with_rect(mask, &top_mid_rect); | |
373 } else { | |
374 GdkRectangle top_rect = { 0, 0, width, 3 }; | |
375 mask = gdk_region_rectangle(&top_rect); | |
376 } | |
377 | |
378 if (corner_style_ & panel::BOTTOM_ROUNDED) { | |
379 GdkRectangle mid_rect = { 0, 3, width, height - 6 }; | |
380 GdkRectangle bottom_mid_rect = { 1, height - 3, width - 2, 2 }; | |
381 GdkRectangle bottom_bottom_rect = { 3, height - 1, width - 6, 1 }; | |
382 gdk_region_union_with_rect(mask, &mid_rect); | |
383 gdk_region_union_with_rect(mask, &bottom_mid_rect); | |
384 gdk_region_union_with_rect(mask, &bottom_bottom_rect); | |
385 } else { | |
386 GdkRectangle mid_rect = { 0, 3, width, height - 3 }; | |
387 gdk_region_union_with_rect(mask, &mid_rect); | |
388 } | |
389 | |
390 gdk_window_shape_combine_region( | |
391 gtk_widget_get_window(GTK_WIDGET(window_)), mask, 0, 0); | |
392 if (mask) | |
393 gdk_region_destroy(mask); | |
394 } | |
395 | |
396 gboolean PanelGtk::OnConfigure(GtkWidget* widget, | |
397 GdkEventConfigure* event) { | |
398 // When the window moves, we'll get multiple configure-event signals. We can | |
399 // also get events when the bounds haven't changed, but the window's stacking | |
400 // has, which we aren't interested in. http://crbug.com/70125 | |
401 gfx::Size new_size(event->width, event->height); | |
402 if (new_size == configure_size_) | |
403 return FALSE; | |
404 configure_size_ = new_size; | |
405 | |
406 UpdateWindowShape(); | |
407 | |
408 if (!GetFrameSize().IsEmpty()) | |
409 return FALSE; | |
410 | |
411 // Save the frame size allocated by the system as the | |
412 // frame size will be affected when we shrink the panel smaller | |
413 // than the frame (e.g. when the panel is minimized). | |
414 SetFrameSize(GetNonClientFrameSize()); | |
415 panel_->OnWindowSizeAvailable(); | |
416 | |
417 content::NotificationService::current()->Notify( | |
418 chrome::NOTIFICATION_PANEL_WINDOW_SIZE_KNOWN, | |
419 content::Source<Panel>(panel_.get()), | |
420 content::NotificationService::NoDetails()); | |
421 | |
422 return FALSE; | |
423 } | |
424 | |
425 gboolean PanelGtk::OnWindowState(GtkWidget* widget, | |
426 GdkEventWindowState* event) { | |
427 is_minimized_ = event->new_window_state & GDK_WINDOW_STATE_ICONIFIED; | |
428 return FALSE; | |
429 } | |
430 | |
431 void PanelGtk::ConnectAccelerators() { | |
432 accel_group_ = gtk_accel_group_new(); | |
433 gtk_window_add_accel_group(window_, accel_group_); | |
434 | |
435 const AcceleratorMap& accelerator_table = GetAcceleratorTable(); | |
436 for (AcceleratorMap::const_iterator iter = accelerator_table.begin(); | |
437 iter != accelerator_table.end(); ++iter) { | |
438 gtk_accel_group_connect( | |
439 accel_group_, | |
440 ui::GetGdkKeyCodeForAccelerator(iter->first), | |
441 ui::GetGdkModifierForAccelerator(iter->first), | |
442 GtkAccelFlags(0), | |
443 g_cclosure_new(G_CALLBACK(OnGtkAccelerator), | |
444 GINT_TO_POINTER(iter->second), NULL)); | |
445 } | |
446 } | |
447 | |
448 void PanelGtk::DisconnectAccelerators() { | |
449 // Disconnecting the keys we connected to our accelerator group frees the | |
450 // closures allocated in ConnectAccelerators. | |
451 const AcceleratorMap& accelerator_table = GetAcceleratorTable(); | |
452 for (AcceleratorMap::const_iterator iter = accelerator_table.begin(); | |
453 iter != accelerator_table.end(); ++iter) { | |
454 gtk_accel_group_disconnect_key( | |
455 accel_group_, | |
456 ui::GetGdkKeyCodeForAccelerator(iter->first), | |
457 ui::GetGdkModifierForAccelerator(iter->first)); | |
458 } | |
459 gtk_window_remove_accel_group(window_, accel_group_); | |
460 g_object_unref(accel_group_); | |
461 accel_group_ = NULL; | |
462 } | |
463 | |
464 // static | |
465 gboolean PanelGtk::OnGtkAccelerator(GtkAccelGroup* accel_group, | |
466 GObject* acceleratable, | |
467 guint keyval, | |
468 GdkModifierType modifier, | |
469 void* user_data) { | |
470 DCHECK(acceleratable); | |
471 int command_id = GPOINTER_TO_INT(user_data); | |
472 PanelGtk* panel_gtk = static_cast<PanelGtk*>( | |
473 g_object_get_qdata(acceleratable, GetPanelWindowQuarkKey())); | |
474 return panel_gtk->panel()->ExecuteCommandIfEnabled(command_id); | |
475 } | |
476 | |
477 gboolean PanelGtk::OnKeyPress(GtkWidget* widget, GdkEventKey* event) { | |
478 // No way to deactivate a window in GTK, so ignore input if window | |
479 // is supposed to be 'inactive'. See comments in DeactivatePanel(). | |
480 if (!is_active_) | |
481 return TRUE; | |
482 | |
483 // Propagate the key event to child widget first, so we don't override | |
484 // their accelerators. | |
485 if (!gtk_window_propagate_key_event(GTK_WINDOW(widget), event)) { | |
486 if (!gtk_window_activate_key(GTK_WINDOW(widget), event)) { | |
487 gtk_bindings_activate_event(GTK_OBJECT(widget), event); | |
488 } | |
489 } | |
490 return TRUE; | |
491 } | |
492 | |
493 bool PanelGtk::GetWindowEdge(int x, int y, GdkWindowEdge* edge) const { | |
494 // Only detect the window edge when panels can be resized by the user. | |
495 // This method is used by the base class to detect when the cursor has | |
496 // hit the window edge in order to change the cursor to a resize cursor | |
497 // and to detect when to initiate a resize drag. | |
498 panel::Resizability resizability = panel_->CanResizeByMouse(); | |
499 if (panel::NOT_RESIZABLE == resizability) | |
500 return false; | |
501 | |
502 int width = bounds_.width(); | |
503 int height = bounds_.height(); | |
504 if (x < kFrameBorderThickness) { | |
505 if (y < kResizeAreaCornerSize - kTopResizeAdjust && | |
506 (resizability & panel::RESIZABLE_TOP_LEFT)) { | |
507 *edge = GDK_WINDOW_EDGE_NORTH_WEST; | |
508 return true; | |
509 } else if (y >= height - kResizeAreaCornerSize && | |
510 (resizability & panel::RESIZABLE_BOTTOM_LEFT)) { | |
511 *edge = GDK_WINDOW_EDGE_SOUTH_WEST; | |
512 return true; | |
513 } | |
514 } else if (x >= width - kFrameBorderThickness) { | |
515 if (y < kResizeAreaCornerSize - kTopResizeAdjust && | |
516 (resizability & panel::RESIZABLE_TOP_RIGHT)) { | |
517 *edge = GDK_WINDOW_EDGE_NORTH_EAST; | |
518 return true; | |
519 } else if (y >= height - kResizeAreaCornerSize && | |
520 (resizability & panel::RESIZABLE_BOTTOM_RIGHT)) { | |
521 *edge = GDK_WINDOW_EDGE_SOUTH_EAST; | |
522 return true; | |
523 } | |
524 } | |
525 | |
526 if (x < kFrameBorderThickness && (resizability & panel::RESIZABLE_LEFT)) { | |
527 *edge = GDK_WINDOW_EDGE_WEST; | |
528 return true; | |
529 } else if (x >= width - kFrameBorderThickness && | |
530 (resizability & panel::RESIZABLE_RIGHT)) { | |
531 *edge = GDK_WINDOW_EDGE_EAST; | |
532 return true; | |
533 } | |
534 | |
535 if (y < kFrameBorderThickness && (resizability & panel::RESIZABLE_TOP)) { | |
536 *edge = GDK_WINDOW_EDGE_NORTH; | |
537 return true; | |
538 } else if (y >= height - kFrameBorderThickness && | |
539 (resizability & panel::RESIZABLE_BOTTOM)) { | |
540 *edge = GDK_WINDOW_EDGE_SOUTH; | |
541 return true; | |
542 } | |
543 | |
544 return false; | |
545 } | |
546 | |
547 gfx::Image PanelGtk::GetFrameBackground() const { | |
548 switch (paint_state_) { | |
549 case PAINT_AS_INACTIVE: | |
550 return GetInactiveBackgroundDefaultImage(); | |
551 case PAINT_AS_ACTIVE: | |
552 return GetActiveBackgroundDefaultImage(); | |
553 case PAINT_AS_MINIMIZED: | |
554 return GetMinimizeBackgroundDefaultImage(); | |
555 case PAINT_FOR_ATTENTION: | |
556 return GetAttentionBackgroundDefaultImage(); | |
557 default: | |
558 NOTREACHED(); | |
559 return GetInactiveBackgroundDefaultImage(); | |
560 } | |
561 } | |
562 | |
563 gboolean PanelGtk::OnCustomFrameExpose(GtkWidget* widget, | |
564 GdkEventExpose* event) { | |
565 TRACE_EVENT0("ui::gtk", "PanelGtk::OnCustomFrameExpose"); | |
566 cairo_t* cr = gdk_cairo_create(gtk_widget_get_window(widget)); | |
567 gdk_cairo_rectangle(cr, &event->area); | |
568 cairo_clip(cr); | |
569 | |
570 // Update the painting state. | |
571 int window_height = gdk_window_get_height(gtk_widget_get_window(widget)); | |
572 if (is_drawing_attention_) | |
573 paint_state_ = PAINT_FOR_ATTENTION; | |
574 else if (window_height <= panel::kMinimizedPanelHeight) | |
575 paint_state_ = PAINT_AS_MINIMIZED; | |
576 else if (is_active_) | |
577 paint_state_ = PAINT_AS_ACTIVE; | |
578 else | |
579 paint_state_ = PAINT_AS_INACTIVE; | |
580 | |
581 // Draw the background. | |
582 gfx::CairoCachedSurface* surface = GetFrameBackground().ToCairo(); | |
583 surface->SetSource(cr, widget, 0, 0); | |
584 cairo_pattern_set_extend(cairo_get_source(cr), CAIRO_EXTEND_REPEAT); | |
585 cairo_rectangle(cr, event->area.x, event->area.y, | |
586 event->area.width, event->area.height); | |
587 cairo_fill(cr); | |
588 | |
589 // Draw the border for the minimized panel only. | |
590 if (paint_state_ == PAINT_AS_MINIMIZED) { | |
591 cairo_move_to(cr, 0, 3); | |
592 cairo_line_to(cr, 1, 2); | |
593 cairo_line_to(cr, 1, 1); | |
594 cairo_line_to(cr, 2, 1); | |
595 cairo_line_to(cr, 3, 0); | |
596 cairo_line_to(cr, event->area.width - 3, 0); | |
597 cairo_line_to(cr, event->area.width - 2, 1); | |
598 cairo_line_to(cr, event->area.width - 1, 1); | |
599 cairo_line_to(cr, event->area.width - 1, 2); | |
600 cairo_line_to(cr, event->area.width - 1, 3); | |
601 cairo_line_to(cr, event->area.width - 1, event->area.height - 1); | |
602 cairo_line_to(cr, 0, event->area.height - 1); | |
603 cairo_close_path(cr); | |
604 cairo_set_source_rgb(cr, | |
605 SkColorGetR(kMinimizeBorderDefaultColor) / 255.0, | |
606 SkColorGetG(kMinimizeBorderDefaultColor) / 255.0, | |
607 SkColorGetB(kMinimizeBorderDefaultColor) / 255.0); | |
608 cairo_set_line_width(cr, 1.0); | |
609 cairo_stroke(cr); | |
610 } | |
611 | |
612 cairo_destroy(cr); | |
613 | |
614 return FALSE; // Allow subwidgets to paint. | |
615 } | |
616 | |
617 void PanelGtk::EnsureDragHelperCreated() { | |
618 if (drag_helper_.get()) | |
619 return; | |
620 | |
621 drag_helper_.reset(new PanelDragGtk(panel_.get())); | |
622 gtk_box_pack_end(GTK_BOX(window_vbox_), drag_helper_->widget(), | |
623 FALSE, FALSE, 0); | |
624 } | |
625 | |
626 gboolean PanelGtk::OnTitlebarButtonPressEvent( | |
627 GtkWidget* widget, GdkEventButton* event) { | |
628 if (event->button != 1) | |
629 return TRUE; | |
630 if (event->type != GDK_BUTTON_PRESS) | |
631 return TRUE; | |
632 | |
633 // If the panel is in a stack, bring all other panels in the stack to the | |
634 // top. | |
635 StackedPanelCollection* stack = panel_->stack(); | |
636 if (stack) { | |
637 for (StackedPanelCollection::Panels::const_iterator iter = | |
638 stack->panels().begin(); | |
639 iter != stack->panels().end(); ++iter) { | |
640 Panel* panel = *iter; | |
641 GtkWindow* gtk_window = panel->GetNativeWindow(); | |
642 // If a panel is collapsed, we make it not to take focus. For such window, | |
643 // it cannot be brought to the top by calling gdk_window_raise. To work | |
644 // around this issue, we make it always-on-top first and then put it back | |
645 // to normal. Note that this trick has been done for all panels in the | |
646 // stack, regardless of whether it is collapsed or not. | |
647 // There is one side-effect to this approach: if the panel being pressed | |
648 // on is collapsed, clicking on the client area of the last active | |
649 // window will not raise it above these panels. | |
650 gtk_window_set_keep_above(gtk_window, true); | |
651 gtk_window_set_keep_above(gtk_window, false); | |
652 } | |
653 } else { | |
654 gdk_window_raise(gtk_widget_get_window(GTK_WIDGET(window_))); | |
655 } | |
656 | |
657 EnsureDragHelperCreated(); | |
658 drag_helper_->InitialTitlebarMousePress(event, titlebar_->widget()); | |
659 return TRUE; | |
660 } | |
661 | |
662 gboolean PanelGtk::OnTitlebarButtonReleaseEvent( | |
663 GtkWidget* widget, GdkEventButton* event) { | |
664 if (event->button != 1) | |
665 return TRUE; | |
666 | |
667 panel_->OnTitlebarClicked((event->state & GDK_CONTROL_MASK) ? | |
668 panel::APPLY_TO_ALL : panel::NO_MODIFIER); | |
669 return TRUE; | |
670 } | |
671 | |
672 gboolean PanelGtk::OnMouseMoveEvent(GtkWidget* widget, | |
673 GdkEventMotion* event) { | |
674 // This method is used to update the mouse cursor when over the edge of the | |
675 // custom frame. If we're over some other widget, do nothing. | |
676 if (event->window != gtk_widget_get_window(widget)) { | |
677 // Reset the cursor. | |
678 if (frame_cursor_) { | |
679 frame_cursor_ = NULL; | |
680 gdk_window_set_cursor(gtk_widget_get_window(GTK_WIDGET(window_)), NULL); | |
681 } | |
682 return FALSE; | |
683 } | |
684 | |
685 // Update the cursor if we're on the custom frame border. | |
686 GdkWindowEdge edge; | |
687 bool has_hit_edge = GetWindowEdge(static_cast<int>(event->x), | |
688 static_cast<int>(event->y), &edge); | |
689 GdkCursorType new_cursor = has_hit_edge ? | |
690 gtk_window_util::GdkWindowEdgeToGdkCursorType(edge) : GDK_LAST_CURSOR; | |
691 GdkCursorType last_cursor = | |
692 frame_cursor_ ? frame_cursor_->type : GDK_LAST_CURSOR; | |
693 | |
694 if (last_cursor != new_cursor) { | |
695 frame_cursor_ = has_hit_edge ? gfx::GetCursor(new_cursor) : NULL; | |
696 gdk_window_set_cursor(gtk_widget_get_window(GTK_WIDGET(window_)), | |
697 frame_cursor_); | |
698 } | |
699 return FALSE; | |
700 } | |
701 | |
702 gboolean PanelGtk::OnButtonPressEvent(GtkWidget* widget, | |
703 GdkEventButton* event) { | |
704 if (event->button != 1 || event->type != GDK_BUTTON_PRESS) | |
705 return FALSE; | |
706 | |
707 // No way to deactivate a window in GTK, so we pretended it is deactivated. | |
708 // See comments in DeactivatePanel(). | |
709 // Mouse click anywhere in window should re-activate window so do it now. | |
710 if (!is_active_) | |
711 panel_->Activate(); | |
712 | |
713 // Make the button press coordinate relative to the panel window. | |
714 int win_x, win_y; | |
715 GdkWindow* gdk_window = gtk_widget_get_window(GTK_WIDGET(window_)); | |
716 gdk_window_get_origin(gdk_window, &win_x, &win_y); | |
717 | |
718 GdkWindowEdge edge; | |
719 gfx::Point point(static_cast<int>(event->x_root - win_x), | |
720 static_cast<int>(event->y_root - win_y)); | |
721 bool has_hit_edge = GetWindowEdge(point.x(), point.y(), &edge); | |
722 if (has_hit_edge) { | |
723 gdk_window_raise(gdk_window); | |
724 EnsureDragHelperCreated(); | |
725 // Resize cursor was set by PanelGtk when mouse moved over window edge. | |
726 GdkCursor* cursor = | |
727 gdk_window_get_cursor(gtk_widget_get_window(GTK_WIDGET(window_))); | |
728 drag_helper_->InitialWindowEdgeMousePress(event, cursor, edge); | |
729 return TRUE; | |
730 } | |
731 | |
732 return FALSE; // Continue to propagate the event. | |
733 } | |
734 | |
735 void PanelGtk::ActiveWindowChanged(GdkWindow* active_window) { | |
736 // Do nothing if we're in the process of closing the panel window. | |
737 if (!window_) | |
738 return; | |
739 | |
740 bool is_active = gtk_widget_get_window(GTK_WIDGET(window_)) == active_window; | |
741 if (is_active == is_active_) | |
742 return; // State did not change. | |
743 | |
744 if (is_active) { | |
745 // If there's an app modal dialog (e.g., JS alert), try to redirect | |
746 // the user's attention to the window owning the dialog. | |
747 if (AppModalDialogQueue::GetInstance()->HasActiveDialog()) { | |
748 AppModalDialogQueue::GetInstance()->ActivateModalDialog(); | |
749 return; | |
750 } | |
751 } | |
752 | |
753 is_active_ = is_active; | |
754 titlebar_->UpdateTextColor(); | |
755 InvalidateWindow(); | |
756 panel_->OnActiveStateChanged(is_active_); | |
757 } | |
758 | |
759 // Callback for the delete event. This event is fired when the user tries to | |
760 // close the window. | |
761 gboolean PanelGtk::OnMainWindowDeleteEvent(GtkWidget* widget, | |
762 GdkEvent* event) { | |
763 ClosePanel(); | |
764 | |
765 // Return true to prevent the gtk window from being destroyed. Close will | |
766 // destroy it for us. | |
767 return TRUE; | |
768 } | |
769 | |
770 void PanelGtk::OnMainWindowDestroy(GtkWidget* widget) { | |
771 // BUG 8712. When we gtk_widget_destroy() in ClosePanel(), this will emit the | |
772 // signal right away, and we will be here (while ClosePanel() is still in the | |
773 // call stack). Let stack unwind before deleting the panel. | |
774 // | |
775 // We don't want to use DeleteSoon() here since it won't work on a nested pump | |
776 // (like in UI tests). | |
777 base::MessageLoop::current()->PostTask( | |
778 FROM_HERE, base::Bind(&base::DeletePointer<PanelGtk>, this)); | |
779 } | |
780 | |
781 void PanelGtk::ShowPanel() { | |
782 gtk_window_present(window_); | |
783 RevealPanel(); | |
784 } | |
785 | |
786 void PanelGtk::ShowPanelInactive() { | |
787 gtk_window_set_focus_on_map(window_, false); | |
788 gtk_widget_show(GTK_WIDGET(window_)); | |
789 RevealPanel(); | |
790 } | |
791 | |
792 void PanelGtk::RevealPanel() { | |
793 DCHECK(!is_shown_); | |
794 is_shown_ = true; | |
795 SetBoundsInternal(bounds_); | |
796 } | |
797 | |
798 gfx::Rect PanelGtk::GetPanelBounds() const { | |
799 return bounds_; | |
800 } | |
801 | |
802 void PanelGtk::SetPanelBounds(const gfx::Rect& bounds) { | |
803 SetBoundsInternal(bounds); | |
804 } | |
805 | |
806 void PanelGtk::SetPanelBoundsInstantly(const gfx::Rect& bounds) { | |
807 SetBoundsInternal(bounds); | |
808 } | |
809 | |
810 void PanelGtk::SetBoundsInternal(const gfx::Rect& bounds) { | |
811 if (is_shown_) { | |
812 gdk_window_move_resize(gtk_widget_get_window(GTK_WIDGET(window_)), | |
813 bounds.x(), bounds.y(), | |
814 bounds.width(), bounds.height()); | |
815 } | |
816 | |
817 bounds_ = bounds; | |
818 | |
819 titlebar_->SendEnterNotifyToCloseButtonIfUnderMouse(); | |
820 panel_->manager()->OnPanelAnimationEnded(panel_.get()); | |
821 } | |
822 | |
823 void PanelGtk::ClosePanel() { | |
824 // We're already closing. Do nothing. | |
825 if (!window_) | |
826 return; | |
827 | |
828 if (!panel_->ShouldCloseWindow()) | |
829 return; | |
830 | |
831 if (drag_helper_.get()) | |
832 drag_helper_.reset(); | |
833 | |
834 if (accel_group_) | |
835 DisconnectAccelerators(); | |
836 | |
837 // Cancel any pending callback from the loading animation timer. | |
838 loading_animation_timer_.Stop(); | |
839 | |
840 if (panel_->GetWebContents()) { | |
841 // Hide the window (so it appears to have closed immediately). | |
842 // When web contents are destroyed, we will be called back again. | |
843 gtk_widget_hide(GTK_WIDGET(window_)); | |
844 panel_->OnWindowClosing(); | |
845 return; | |
846 } | |
847 | |
848 GtkWidget* window = GTK_WIDGET(window_); | |
849 // To help catch bugs in any event handlers that might get fired during the | |
850 // destruction, set window_ to NULL before any handlers will run. | |
851 window_ = NULL; | |
852 | |
853 panel_->OnNativePanelClosed(); | |
854 | |
855 // We don't want GlobalMenuBar handling any notifications or commands after | |
856 // the window is destroyed. | |
857 // TODO(jennb): global_menu_bar_->Disable(); | |
858 gtk_widget_destroy(window); | |
859 } | |
860 | |
861 void PanelGtk::ActivatePanel() { | |
862 gtk_window_present(window_); | |
863 | |
864 // When the user clicks to expand the minimized panel, the panel has already | |
865 // become an active window before gtk_window_present is called. Thus the | |
866 // active window change event, fired by ActiveWindowWatcherXObserver, is not | |
867 // triggered. We need to call ActiveWindowChanged manually to update panel's | |
868 // active status. It is OK to call ActiveWindowChanged with the same active | |
869 // window twice since the 2nd call is just a no-op. | |
870 ActiveWindowChanged(gtk_widget_get_window(GTK_WIDGET(window_))); | |
871 } | |
872 | |
873 void PanelGtk::DeactivatePanel() { | |
874 // When a panel is deactivated, it should not be lowered to the bottom of the | |
875 // z-order. We could put it behind other panel window. | |
876 Panel* other_panel = NULL; | |
877 // First, try to pick the sibling panel in the same stack. | |
878 StackedPanelCollection* stack = panel_->stack(); | |
879 if (stack && stack->num_panels()) { | |
880 other_panel = panel_ != stack->top_panel() ? stack->top_panel() | |
881 : stack->bottom_panel(); | |
882 } | |
883 // Then, try to pick other detached or stacked panel. | |
884 if (!other_panel) { | |
885 std::vector<Panel*> panels = | |
886 panel_->manager()->GetDetachedAndStackedPanels(); | |
887 if (!panels.empty()) | |
888 other_panel = panel_ != panels.front() ? panels.front() : panels.back(); | |
889 } | |
890 | |
891 gdk_window_restack( | |
892 gtk_widget_get_window(GTK_WIDGET(window_)), | |
893 other_panel ? gtk_widget_get_window( | |
894 GTK_WIDGET(other_panel->GetNativeWindow())) : NULL, | |
895 false); | |
896 | |
897 // Per ICCCM: http://tronche.com/gui/x/icccm/sec-4.html#s-4.1.7 | |
898 // A convention is also required for clients that want to give up the | |
899 // input focus. There is no safe value set for them to set the input | |
900 // focus to; therefore, they should ignore input material. | |
901 // | |
902 // No way to deactive a GTK window. Pretend panel is deactivated | |
903 // and ignore input. | |
904 ActiveWindowChanged(NULL); | |
905 } | |
906 | |
907 bool PanelGtk::IsPanelActive() const { | |
908 return is_active_; | |
909 } | |
910 | |
911 void PanelGtk::PreventActivationByOS(bool prevent_activation) { | |
912 gtk_window_set_accept_focus(window_, !prevent_activation); | |
913 } | |
914 | |
915 gfx::NativeWindow PanelGtk::GetNativePanelWindow() { | |
916 return window_; | |
917 } | |
918 | |
919 void PanelGtk::UpdatePanelTitleBar() { | |
920 TRACE_EVENT0("ui::gtk", "PanelGtk::UpdatePanelTitleBar"); | |
921 base::string16 title = panel_->GetWindowTitle(); | |
922 gtk_window_set_title(window_, base::UTF16ToUTF8(title).c_str()); | |
923 titlebar_->UpdateTitleAndIcon(); | |
924 | |
925 gfx::Image app_icon = panel_->app_icon(); | |
926 if (!app_icon.IsEmpty()) | |
927 gtk_util::SetWindowIcon(window_, panel_->profile(), app_icon.ToGdkPixbuf()); | |
928 } | |
929 | |
930 void PanelGtk::UpdatePanelLoadingAnimations(bool should_animate) { | |
931 if (should_animate) { | |
932 if (!loading_animation_timer_.IsRunning()) { | |
933 // Loads are happening, and the timer isn't running, so start it. | |
934 loading_animation_timer_.Start(FROM_HERE, | |
935 base::TimeDelta::FromMilliseconds(kLoadingAnimationFrameTimeMs), | |
936 this, | |
937 &PanelGtk::LoadingAnimationCallback); | |
938 } | |
939 } else { | |
940 if (loading_animation_timer_.IsRunning()) { | |
941 loading_animation_timer_.Stop(); | |
942 // Loads are now complete, update the state if a task was scheduled. | |
943 LoadingAnimationCallback(); | |
944 } | |
945 } | |
946 } | |
947 | |
948 void PanelGtk::LoadingAnimationCallback() { | |
949 titlebar_->UpdateThrobber(panel_->GetWebContents()); | |
950 } | |
951 | |
952 void PanelGtk::PanelWebContentsFocused(content::WebContents* contents) { | |
953 // Nothing to do. | |
954 } | |
955 | |
956 void PanelGtk::PanelCut() { | |
957 gtk_window_util::DoCut(window_, panel_->GetWebContents()); | |
958 } | |
959 | |
960 void PanelGtk::PanelCopy() { | |
961 gtk_window_util::DoCopy(window_, panel_->GetWebContents()); | |
962 } | |
963 | |
964 void PanelGtk::PanelPaste() { | |
965 gtk_window_util::DoPaste(window_, panel_->GetWebContents()); | |
966 } | |
967 | |
968 void PanelGtk::DrawAttention(bool draw_attention) { | |
969 DCHECK((panel_->attention_mode() & Panel::USE_PANEL_ATTENTION) != 0); | |
970 | |
971 if (is_drawing_attention_ == draw_attention) | |
972 return; | |
973 | |
974 is_drawing_attention_ = draw_attention; | |
975 | |
976 titlebar_->UpdateTextColor(); | |
977 InvalidateWindow(); | |
978 | |
979 if ((panel_->attention_mode() & Panel::USE_SYSTEM_ATTENTION) != 0) { | |
980 // May not be respected by all window managers. | |
981 gtk_window_set_urgency_hint(window_, draw_attention); | |
982 } | |
983 } | |
984 | |
985 bool PanelGtk::IsDrawingAttention() const { | |
986 return is_drawing_attention_; | |
987 } | |
988 | |
989 void PanelGtk::HandlePanelKeyboardEvent( | |
990 const NativeWebKeyboardEvent& event) { | |
991 GdkEventKey* os_event = &event.os_event->key; | |
992 if (os_event && event.type == blink::WebInputEvent::RawKeyDown) | |
993 gtk_window_activate_key(window_, os_event); | |
994 } | |
995 | |
996 void PanelGtk::FullScreenModeChanged(bool is_full_screen) { | |
997 // No need to hide panels when entering the full-screen mode because the | |
998 // full-screen window will automatically be placed above all other windows. | |
999 if (is_full_screen) | |
1000 return; | |
1001 | |
1002 // Show the panel if not yet when leaving the full-screen mode. This is | |
1003 // because the panel is not shown when it is being created under full-screen | |
1004 // mode. | |
1005 GdkWindow* gdk_window = gtk_widget_get_window(GTK_WIDGET(window_)); | |
1006 if (!GDK_IS_WINDOW(gdk_window) || !gdk_window_is_visible(gdk_window)) | |
1007 ShowPanelInactive(); | |
1008 } | |
1009 | |
1010 void PanelGtk::PanelExpansionStateChanging( | |
1011 Panel::ExpansionState old_state, Panel::ExpansionState new_state) { | |
1012 } | |
1013 | |
1014 void PanelGtk::AttachWebContents(content::WebContents* contents) { | |
1015 if (!contents) | |
1016 return; | |
1017 gfx::NativeView widget = contents->GetView()->GetNativeView(); | |
1018 if (widget) { | |
1019 gtk_container_add(GTK_CONTAINER(contents_expanded_), widget); | |
1020 gtk_widget_show(widget); | |
1021 contents->WasShown(); | |
1022 } | |
1023 } | |
1024 | |
1025 void PanelGtk::DetachWebContents(content::WebContents* contents) { | |
1026 gfx::NativeView widget = contents->GetView()->GetNativeView(); | |
1027 if (widget) { | |
1028 GtkWidget* parent = gtk_widget_get_parent(widget); | |
1029 if (parent) { | |
1030 DCHECK_EQ(parent, contents_expanded_); | |
1031 gtk_container_remove(GTK_CONTAINER(contents_expanded_), widget); | |
1032 } | |
1033 } | |
1034 } | |
1035 | |
1036 gfx::Size PanelGtk::WindowSizeFromContentSize( | |
1037 const gfx::Size& content_size) const { | |
1038 gfx::Size& frame_size = GetFrameSize(); | |
1039 return gfx::Size(content_size.width() + frame_size.width(), | |
1040 content_size.height() + frame_size.height()); | |
1041 } | |
1042 | |
1043 gfx::Size PanelGtk::ContentSizeFromWindowSize( | |
1044 const gfx::Size& window_size) const { | |
1045 gfx::Size& frame_size = GetFrameSize(); | |
1046 return gfx::Size(window_size.width() - frame_size.width(), | |
1047 window_size.height() - frame_size.height()); | |
1048 } | |
1049 | |
1050 int PanelGtk::TitleOnlyHeight() const { | |
1051 gfx::Size& frame_size = GetFrameSize(); | |
1052 if (!frame_size.IsEmpty()) | |
1053 return panel::kTitlebarHeight; | |
1054 | |
1055 NOTREACHED() << "Checking title height before window allocated"; | |
1056 return 0; | |
1057 } | |
1058 | |
1059 bool PanelGtk::IsPanelAlwaysOnTop() const { | |
1060 return always_on_top_; | |
1061 } | |
1062 | |
1063 void PanelGtk::SetPanelAlwaysOnTop(bool on_top) { | |
1064 always_on_top_ = on_top; | |
1065 | |
1066 gtk_window_set_keep_above(window_, on_top); | |
1067 | |
1068 // Do not show an icon in the task bar for always-on-top windows. | |
1069 // Window operations such as close, minimize etc. can only be done | |
1070 // from the panel UI. | |
1071 gtk_window_set_skip_taskbar_hint(window_, on_top); | |
1072 | |
1073 // Show always-on-top windows on all the virtual desktops. | |
1074 if (on_top) | |
1075 gtk_window_stick(window_); | |
1076 else | |
1077 gtk_window_unstick(window_); | |
1078 } | |
1079 | |
1080 void PanelGtk::UpdatePanelMinimizeRestoreButtonVisibility() { | |
1081 titlebar_->UpdateMinimizeRestoreButtonVisibility(); | |
1082 } | |
1083 | |
1084 gfx::Size PanelGtk::GetNonClientFrameSize() const { | |
1085 GtkAllocation window_allocation; | |
1086 gtk_widget_get_allocation(window_container_, &window_allocation); | |
1087 GtkAllocation contents_allocation; | |
1088 gtk_widget_get_allocation(contents_expanded_, &contents_allocation); | |
1089 return gfx::Size(window_allocation.width - contents_allocation.width, | |
1090 window_allocation.height - contents_allocation.height); | |
1091 } | |
1092 | |
1093 void PanelGtk::InvalidateWindow() { | |
1094 GtkAllocation allocation; | |
1095 gtk_widget_get_allocation(GTK_WIDGET(window_), &allocation); | |
1096 gdk_window_invalidate_rect(gtk_widget_get_window(GTK_WIDGET(window_)), | |
1097 &allocation, TRUE); | |
1098 } | |
1099 | |
1100 // NativePanelTesting implementation. | |
1101 class GtkNativePanelTesting : public NativePanelTesting { | |
1102 public: | |
1103 explicit GtkNativePanelTesting(PanelGtk* panel_gtk); | |
1104 | |
1105 private: | |
1106 virtual void PressLeftMouseButtonTitlebar( | |
1107 const gfx::Point& mouse_location, panel::ClickModifier modifier) OVERRIDE; | |
1108 virtual void ReleaseMouseButtonTitlebar( | |
1109 panel::ClickModifier modifier) OVERRIDE; | |
1110 virtual void DragTitlebar(const gfx::Point& mouse_location) OVERRIDE; | |
1111 virtual void CancelDragTitlebar() OVERRIDE; | |
1112 virtual void FinishDragTitlebar() OVERRIDE; | |
1113 virtual bool VerifyDrawingAttention() const OVERRIDE; | |
1114 virtual bool VerifyActiveState(bool is_active) OVERRIDE; | |
1115 virtual bool VerifyAppIcon() const OVERRIDE; | |
1116 virtual bool VerifySystemMinimizeState() const OVERRIDE; | |
1117 virtual bool IsWindowVisible() const OVERRIDE; | |
1118 virtual bool IsWindowSizeKnown() const OVERRIDE; | |
1119 virtual bool IsAnimatingBounds() const OVERRIDE; | |
1120 virtual bool IsButtonVisible( | |
1121 panel::TitlebarButtonType button_type) const OVERRIDE; | |
1122 virtual panel::CornerStyle GetWindowCornerStyle() const OVERRIDE; | |
1123 virtual bool EnsureApplicationRunOnForeground() OVERRIDE; | |
1124 | |
1125 PanelGtk* panel_gtk_; | |
1126 }; | |
1127 | |
1128 NativePanelTesting* PanelGtk::CreateNativePanelTesting() { | |
1129 return new GtkNativePanelTesting(this); | |
1130 } | |
1131 | |
1132 GtkNativePanelTesting::GtkNativePanelTesting(PanelGtk* panel_gtk) | |
1133 : panel_gtk_(panel_gtk) { | |
1134 } | |
1135 | |
1136 void GtkNativePanelTesting::PressLeftMouseButtonTitlebar( | |
1137 const gfx::Point& mouse_location, panel::ClickModifier modifier) { | |
1138 | |
1139 GdkEvent* event = gdk_event_new(GDK_BUTTON_PRESS); | |
1140 event->button.button = 1; | |
1141 event->button.x_root = mouse_location.x(); | |
1142 event->button.y_root = mouse_location.y(); | |
1143 if (modifier == panel::APPLY_TO_ALL) | |
1144 event->button.state |= GDK_CONTROL_MASK; | |
1145 panel_gtk_->OnTitlebarButtonPressEvent( | |
1146 NULL, reinterpret_cast<GdkEventButton*>(event)); | |
1147 gdk_event_free(event); | |
1148 base::MessageLoopForUI::current()->RunUntilIdle(); | |
1149 } | |
1150 | |
1151 void GtkNativePanelTesting::ReleaseMouseButtonTitlebar( | |
1152 panel::ClickModifier modifier) { | |
1153 GdkEvent* event = gdk_event_new(GDK_BUTTON_RELEASE); | |
1154 event->button.button = 1; | |
1155 if (modifier == panel::APPLY_TO_ALL) | |
1156 event->button.state |= GDK_CONTROL_MASK; | |
1157 if (panel_gtk_->drag_helper_.get()) { | |
1158 panel_gtk_->drag_helper_->OnButtonReleaseEvent( | |
1159 NULL, reinterpret_cast<GdkEventButton*>(event)); | |
1160 } else { | |
1161 panel_gtk_->OnTitlebarButtonReleaseEvent( | |
1162 NULL, reinterpret_cast<GdkEventButton*>(event)); | |
1163 } | |
1164 gdk_event_free(event); | |
1165 base::MessageLoopForUI::current()->RunUntilIdle(); | |
1166 } | |
1167 | |
1168 void GtkNativePanelTesting::DragTitlebar(const gfx::Point& mouse_location) { | |
1169 if (!panel_gtk_->drag_helper_.get()) | |
1170 return; | |
1171 GdkEvent* event = gdk_event_new(GDK_MOTION_NOTIFY); | |
1172 event->motion.x_root = mouse_location.x(); | |
1173 event->motion.y_root = mouse_location.y(); | |
1174 panel_gtk_->drag_helper_->OnMouseMoveEvent( | |
1175 NULL, reinterpret_cast<GdkEventMotion*>(event)); | |
1176 gdk_event_free(event); | |
1177 base::MessageLoopForUI::current()->RunUntilIdle(); | |
1178 } | |
1179 | |
1180 void GtkNativePanelTesting::CancelDragTitlebar() { | |
1181 if (!panel_gtk_->drag_helper_.get()) | |
1182 return; | |
1183 panel_gtk_->drag_helper_->OnGrabBrokenEvent(NULL, NULL); | |
1184 base::MessageLoopForUI::current()->RunUntilIdle(); | |
1185 } | |
1186 | |
1187 void GtkNativePanelTesting::FinishDragTitlebar() { | |
1188 if (!panel_gtk_->drag_helper_.get()) | |
1189 return; | |
1190 ReleaseMouseButtonTitlebar(panel::NO_MODIFIER); | |
1191 } | |
1192 | |
1193 bool GtkNativePanelTesting::VerifyDrawingAttention() const { | |
1194 return panel_gtk_->IsDrawingAttention(); | |
1195 } | |
1196 | |
1197 bool GtkNativePanelTesting::VerifyActiveState(bool is_active) { | |
1198 return gtk_window_is_active(panel_gtk_->GetNativePanelWindow()) == is_active; | |
1199 } | |
1200 | |
1201 bool GtkNativePanelTesting::VerifyAppIcon() const { | |
1202 GdkPixbuf* icon = gtk_window_get_icon(panel_gtk_->GetNativePanelWindow()); | |
1203 return icon && | |
1204 gdk_pixbuf_get_width(icon) == panel::kPanelAppIconSize && | |
1205 gdk_pixbuf_get_height(icon) == panel::kPanelAppIconSize; | |
1206 } | |
1207 | |
1208 bool GtkNativePanelTesting::VerifySystemMinimizeState() const { | |
1209 // TODO(jianli): to be implemented. | |
1210 return true; | |
1211 } | |
1212 | |
1213 bool GtkNativePanelTesting::IsWindowVisible() const { | |
1214 GdkWindow* gdk_window = | |
1215 gtk_widget_get_window(GTK_WIDGET(panel_gtk_->GetNativePanelWindow())); | |
1216 return GDK_IS_WINDOW(gdk_window) && gdk_window_is_visible(gdk_window); | |
1217 } | |
1218 | |
1219 bool GtkNativePanelTesting::IsWindowSizeKnown() const { | |
1220 return !GetFrameSize().IsEmpty(); | |
1221 } | |
1222 | |
1223 bool GtkNativePanelTesting::IsAnimatingBounds() const { | |
1224 return false; | |
1225 } | |
1226 | |
1227 bool GtkNativePanelTesting::IsButtonVisible( | |
1228 panel::TitlebarButtonType button_type) const { | |
1229 PanelTitlebarGtk* titlebar = panel_gtk_->titlebar(); | |
1230 CustomDrawButton* button; | |
1231 switch (button_type) { | |
1232 case panel::CLOSE_BUTTON: | |
1233 button = titlebar->close_button(); | |
1234 break; | |
1235 case panel::MINIMIZE_BUTTON: | |
1236 button = titlebar->minimize_button(); | |
1237 break; | |
1238 case panel::RESTORE_BUTTON: | |
1239 button = titlebar->restore_button(); | |
1240 break; | |
1241 default: | |
1242 NOTREACHED(); | |
1243 return false; | |
1244 } | |
1245 return gtk_widget_get_visible(button->widget()); | |
1246 } | |
1247 | |
1248 panel::CornerStyle GtkNativePanelTesting::GetWindowCornerStyle() const { | |
1249 return panel_gtk_->corner_style_; | |
1250 } | |
1251 | |
1252 bool GtkNativePanelTesting::EnsureApplicationRunOnForeground() { | |
1253 // Not needed on GTK. | |
1254 return true; | |
1255 } | |
OLD | NEW |