[email protected] | 534b163 | 2012-08-14 18:54:48 | [diff] [blame] | 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/gtk_window_util.h" |
| 6 | |
| 7 | #include <dlfcn.h> |
| 8 | #include "content/public/browser/render_view_host.h" |
| 9 | #include "content/public/browser/web_contents.h" |
| 10 | |
| 11 | using content::RenderWidgetHost; |
| 12 | using content::WebContents; |
| 13 | |
| 14 | namespace gtk_window_util { |
| 15 | |
[email protected] | e3fcf7e | 2012-08-21 07:04:44 | [diff] [blame^] | 16 | // Keep track of the last click time and the last click position so we can |
| 17 | // filter out extra GDK_BUTTON_PRESS events when a double click happens. |
| 18 | static guint32 last_click_time; |
| 19 | static int last_click_x; |
| 20 | static int last_click_y; |
| 21 | |
[email protected] | 534b163 | 2012-08-14 18:54:48 | [diff] [blame] | 22 | // Performs Cut/Copy/Paste operation on the |window|. |
| 23 | // If the current render view is focused, then just call the specified |method| |
| 24 | // against the current render view host, otherwise emit the specified |signal| |
| 25 | // against the focused widget. |
| 26 | // TODO(suzhe): This approach does not work for plugins. |
| 27 | void DoCutCopyPaste(GtkWindow* window, |
| 28 | WebContents* web_contents, |
| 29 | void (RenderWidgetHost::*method)(), |
| 30 | const char* signal) { |
| 31 | GtkWidget* widget = gtk_window_get_focus(window); |
| 32 | if (widget == NULL) |
| 33 | return; // Do nothing if no focused widget. |
| 34 | |
| 35 | if (web_contents && widget == web_contents->GetContentNativeView()) { |
| 36 | (web_contents->GetRenderViewHost()->*method)(); |
| 37 | } else { |
| 38 | guint id; |
| 39 | if ((id = g_signal_lookup(signal, G_OBJECT_TYPE(widget))) != 0) |
| 40 | g_signal_emit(widget, id, 0); |
| 41 | } |
| 42 | } |
| 43 | |
| 44 | void DoCut(GtkWindow* window, WebContents* web_contents) { |
| 45 | DoCutCopyPaste(window, web_contents, |
| 46 | &RenderWidgetHost::Cut, "cut-clipboard"); |
| 47 | } |
| 48 | |
| 49 | void DoCopy(GtkWindow* window, WebContents* web_contents) { |
| 50 | DoCutCopyPaste(window, web_contents, |
| 51 | &RenderWidgetHost::Copy, "copy-clipboard"); |
| 52 | } |
| 53 | |
| 54 | void DoPaste(GtkWindow* window, WebContents* web_contents) { |
| 55 | DoCutCopyPaste(window, web_contents, |
| 56 | &RenderWidgetHost::Paste, "paste-clipboard"); |
| 57 | } |
| 58 | |
| 59 | // Ubuntu patches their version of GTK+ so that there is always a |
| 60 | // gripper in the bottom right corner of the window. We dynamically |
| 61 | // look up this symbol because it's a non-standard Ubuntu extension to |
| 62 | // GTK+. We always need to disable this feature since we can't |
| 63 | // communicate this to WebKit easily. |
| 64 | typedef void (*gtk_window_set_has_resize_grip_func)(GtkWindow*, gboolean); |
| 65 | gtk_window_set_has_resize_grip_func gtk_window_set_has_resize_grip_sym; |
| 66 | |
| 67 | void DisableResizeGrip(GtkWindow* window) { |
| 68 | static bool resize_grip_looked_up = false; |
| 69 | if (!resize_grip_looked_up) { |
| 70 | resize_grip_looked_up = true; |
| 71 | gtk_window_set_has_resize_grip_sym = |
| 72 | reinterpret_cast<gtk_window_set_has_resize_grip_func>( |
| 73 | dlsym(NULL, "gtk_window_set_has_resize_grip")); |
| 74 | } |
| 75 | if (gtk_window_set_has_resize_grip_sym) |
| 76 | gtk_window_set_has_resize_grip_sym(window, FALSE); |
| 77 | } |
| 78 | |
| 79 | GdkCursorType GdkWindowEdgeToGdkCursorType(GdkWindowEdge edge) { |
| 80 | switch (edge) { |
| 81 | case GDK_WINDOW_EDGE_NORTH_WEST: |
| 82 | return GDK_TOP_LEFT_CORNER; |
| 83 | case GDK_WINDOW_EDGE_NORTH: |
| 84 | return GDK_TOP_SIDE; |
| 85 | case GDK_WINDOW_EDGE_NORTH_EAST: |
| 86 | return GDK_TOP_RIGHT_CORNER; |
| 87 | case GDK_WINDOW_EDGE_WEST: |
| 88 | return GDK_LEFT_SIDE; |
| 89 | case GDK_WINDOW_EDGE_EAST: |
| 90 | return GDK_RIGHT_SIDE; |
| 91 | case GDK_WINDOW_EDGE_SOUTH_WEST: |
| 92 | return GDK_BOTTOM_LEFT_CORNER; |
| 93 | case GDK_WINDOW_EDGE_SOUTH: |
| 94 | return GDK_BOTTOM_SIDE; |
| 95 | case GDK_WINDOW_EDGE_SOUTH_EAST: |
| 96 | return GDK_BOTTOM_RIGHT_CORNER; |
| 97 | default: |
| 98 | NOTREACHED(); |
| 99 | } |
| 100 | return GDK_LAST_CURSOR; |
| 101 | } |
| 102 | |
[email protected] | e3fcf7e | 2012-08-21 07:04:44 | [diff] [blame^] | 103 | bool BoundsMatchMonitorSize(GtkWindow* window, gfx::Rect bounds) { |
| 104 | // A screen can be composed of multiple monitors. |
| 105 | GdkScreen* screen = gtk_window_get_screen(window); |
| 106 | gint monitor_num = gdk_screen_get_monitor_at_window(screen, |
| 107 | gtk_widget_get_window(GTK_WIDGET(window))); |
| 108 | |
| 109 | GdkRectangle monitor_size; |
| 110 | gdk_screen_get_monitor_geometry(screen, monitor_num, &monitor_size); |
| 111 | return bounds.size() == gfx::Size(monitor_size.width, monitor_size.height); |
| 112 | } |
| 113 | |
| 114 | bool HandleTitleBarLeftMousePress( |
| 115 | GtkWindow* window, |
| 116 | const gfx::Rect& bounds, |
| 117 | GdkEventButton* event) { |
| 118 | // We want to start a move when the user single clicks, but not start a |
| 119 | // move when the user double clicks. However, a double click sends the |
| 120 | // following GDK events: GDK_BUTTON_PRESS, GDK_BUTTON_RELEASE, |
| 121 | // GDK_BUTTON_PRESS, GDK_2BUTTON_PRESS, GDK_BUTTON_RELEASE. If we |
| 122 | // start a gtk_window_begin_move_drag on the second GDK_BUTTON_PRESS, |
| 123 | // the call to gtk_window_maximize fails. To work around this, we |
| 124 | // keep track of the last click and if it's going to be a double click, |
| 125 | // we don't call gtk_window_begin_move_drag. |
| 126 | DCHECK(event->type == GDK_BUTTON_PRESS); |
| 127 | DCHECK(event->button == 1); |
| 128 | |
| 129 | static GtkSettings* settings = gtk_settings_get_default(); |
| 130 | gint double_click_time = 250; |
| 131 | gint double_click_distance = 5; |
| 132 | g_object_get(G_OBJECT(settings), |
| 133 | "gtk-double-click-time", &double_click_time, |
| 134 | "gtk-double-click-distance", &double_click_distance, |
| 135 | NULL); |
| 136 | |
| 137 | guint32 click_time = event->time - last_click_time; |
| 138 | int click_move_x = abs(event->x - last_click_x); |
| 139 | int click_move_y = abs(event->y - last_click_y); |
| 140 | |
| 141 | last_click_time = event->time; |
| 142 | last_click_x = static_cast<int>(event->x); |
| 143 | last_click_y = static_cast<int>(event->y); |
| 144 | |
| 145 | if (click_time > static_cast<guint32>(double_click_time) || |
| 146 | click_move_x > double_click_distance || |
| 147 | click_move_y > double_click_distance) { |
| 148 | // Ignore drag requests if the window is the size of the screen. |
| 149 | // We do this to avoid triggering fullscreen mode in metacity |
| 150 | // (without the --no-force-fullscreen flag) and in compiz (with |
| 151 | // Legacy Fullscreen Mode enabled). |
| 152 | if (!BoundsMatchMonitorSize(window, bounds)) { |
| 153 | gtk_window_begin_move_drag(window, event->button, |
| 154 | static_cast<gint>(event->x_root), |
| 155 | static_cast<gint>(event->y_root), |
| 156 | event->time); |
| 157 | } |
| 158 | return TRUE; |
| 159 | } |
| 160 | return FALSE; |
| 161 | } |
| 162 | |
| 163 | void UnMaximize(GtkWindow* window, |
| 164 | const gfx::Rect& bounds, |
| 165 | const gfx::Rect& restored_bounds) { |
| 166 | gtk_window_unmaximize(window); |
| 167 | |
| 168 | // It can happen that you end up with a window whose restore size is the same |
| 169 | // as the size of the screen, so unmaximizing it merely remaximizes it due to |
| 170 | // the same WM feature that SetWindowSize() works around. We try to detect |
| 171 | // this and resize the window to work around the issue. |
| 172 | if (bounds.size() == restored_bounds.size()) |
| 173 | gtk_window_resize(window, bounds.width(), bounds.height() - 1); |
| 174 | } |
| 175 | |
[email protected] | 534b163 | 2012-08-14 18:54:48 | [diff] [blame] | 176 | } // namespace gtk_window_util |