[email protected] | 3361e1f | 2012-03-20 20:31:44 | [diff] [blame] | 1 | // Copyright (c) 2012 The Chromium Authors. All rights reserved. |
[email protected] | f41095f8 | 2011-06-07 16:57:44 | [diff] [blame] | 2 | // Use of this source code is governed by a BSD-style license that can be |
| 3 | // found in the LICENSE file. |
| 4 | |
[email protected] | 456405f | 2011-06-13 20:42:51 | [diff] [blame] | 5 | #include <gtk/gtk.h> |
[email protected] | a0117df | 2011-12-03 00:38:41 | [diff] [blame] | 6 | #include <math.h> |
[email protected] | 456405f | 2011-06-13 20:42:51 | [diff] [blame] | 7 | |
[email protected] | f41095f8 | 2011-06-07 16:57:44 | [diff] [blame] | 8 | #include "base/compiler_specific.h" |
| 9 | #include "base/logging.h" |
[email protected] | eaf9253 | 2013-06-11 07:39:19 | [diff] [blame] | 10 | #include "base/strings/string_util.h" |
[email protected] | 90626587 | 2013-06-07 22:40:45 | [diff] [blame] | 11 | #include "base/strings/utf_string_conversions.h" |
[email protected] | fc877cf | 2013-07-31 23:08:39 | [diff] [blame] | 12 | #include "remoting/base/string_resources.h" |
[email protected] | c308f512 | 2013-04-05 03:32:06 | [diff] [blame] | 13 | #include "remoting/host/client_session_control.h" |
| 14 | #include "remoting/host/host_window.h" |
[email protected] | b8c35dd9b | 2014-04-16 02:54:17 | [diff] [blame] | 15 | #include "ui/base/glib/glib_signal.h" |
[email protected] | fc877cf | 2013-07-31 23:08:39 | [diff] [blame] | 16 | #include "ui/base/l10n/l10n_util.h" |
[email protected] | f41095f8 | 2011-06-07 16:57:44 | [diff] [blame] | 17 | |
[email protected] | edd125d8 | 2011-06-28 22:47:36 | [diff] [blame] | 18 | namespace remoting { |
[email protected] | 456405f | 2011-06-13 20:42:51 | [diff] [blame] | 19 | |
[email protected] | c308f512 | 2013-04-05 03:32:06 | [diff] [blame] | 20 | namespace { |
| 21 | |
| 22 | class DisconnectWindowGtk : public HostWindow { |
[email protected] | f41095f8 | 2011-06-07 16:57:44 | [diff] [blame] | 23 | public: |
[email protected] | fc877cf | 2013-07-31 23:08:39 | [diff] [blame] | 24 | DisconnectWindowGtk(); |
dcheng | 440d8e1c | 2014-10-28 01:23:15 | [diff] [blame] | 25 | ~DisconnectWindowGtk() override; |
[email protected] | 456405f | 2011-06-13 20:42:51 | [diff] [blame] | 26 | |
[email protected] | c308f512 | 2013-04-05 03:32:06 | [diff] [blame] | 27 | // HostWindow overrides. |
dcheng | 440d8e1c | 2014-10-28 01:23:15 | [diff] [blame] | 28 | void Start(const base::WeakPtr<ClientSessionControl>& client_session_control) |
mostynb | 11d989c | 2014-10-08 16:58:09 | [diff] [blame] | 29 | override; |
[email protected] | f41095f8 | 2011-06-07 16:57:44 | [diff] [blame] | 30 | |
| 31 | private: |
[email protected] | b8c35dd9b | 2014-04-16 02:54:17 | [diff] [blame] | 32 | CHROMEG_CALLBACK_1(DisconnectWindowGtk, gboolean, OnDelete, |
| 33 | GtkWidget*, GdkEvent*); |
| 34 | CHROMEG_CALLBACK_0(DisconnectWindowGtk, void, OnClicked, GtkButton*); |
| 35 | CHROMEG_CALLBACK_1(DisconnectWindowGtk, gboolean, OnConfigure, |
| 36 | GtkWidget*, GdkEventConfigure*); |
| 37 | CHROMEG_CALLBACK_1(DisconnectWindowGtk, gboolean, OnButtonPress, |
| 38 | GtkWidget*, GdkEventButton*); |
[email protected] | 456405f | 2011-06-13 20:42:51 | [diff] [blame] | 39 | |
[email protected] | c308f512 | 2013-04-05 03:32:06 | [diff] [blame] | 40 | // Used to disconnect the client session. |
| 41 | base::WeakPtr<ClientSessionControl> client_session_control_; |
[email protected] | 456405f | 2011-06-13 20:42:51 | [diff] [blame] | 42 | |
[email protected] | 456405f | 2011-06-13 20:42:51 | [diff] [blame] | 43 | GtkWidget* disconnect_window_; |
[email protected] | d600e2d | 2011-08-16 17:45:05 | [diff] [blame] | 44 | GtkWidget* message_; |
[email protected] | a0117df | 2011-12-03 00:38:41 | [diff] [blame] | 45 | GtkWidget* button_; |
| 46 | |
| 47 | // Used to distinguish resize events from other types of "configure-event" |
| 48 | // notifications. |
| 49 | int current_width_; |
| 50 | int current_height_; |
[email protected] | 456405f | 2011-06-13 20:42:51 | [diff] [blame] | 51 | |
[email protected] | 8b8f0b2 | 2012-05-17 01:58:46 | [diff] [blame] | 52 | DISALLOW_COPY_AND_ASSIGN(DisconnectWindowGtk); |
[email protected] | f41095f8 | 2011-06-07 16:57:44 | [diff] [blame] | 53 | }; |
[email protected] | f41095f8 | 2011-06-07 16:57:44 | [diff] [blame] | 54 | |
[email protected] | c308f512 | 2013-04-05 03:32:06 | [diff] [blame] | 55 | // Helper function for creating a rectangular path with rounded corners, as |
| 56 | // Cairo doesn't have this facility. |radius| is the arc-radius of each |
| 57 | // corner. The bounding rectangle extends from (0, 0) to (width, height). |
| 58 | void AddRoundRectPath(cairo_t* cairo_context, int width, int height, |
| 59 | int radius) { |
| 60 | cairo_new_sub_path(cairo_context); |
| 61 | cairo_arc(cairo_context, width - radius, radius, radius, -M_PI_2, 0); |
| 62 | cairo_arc(cairo_context, width - radius, height - radius, radius, 0, M_PI_2); |
| 63 | cairo_arc(cairo_context, radius, height - radius, radius, M_PI_2, 2 * M_PI_2); |
| 64 | cairo_arc(cairo_context, radius, radius, radius, 2 * M_PI_2, 3 * M_PI_2); |
| 65 | cairo_close_path(cairo_context); |
| 66 | } |
| 67 | |
[email protected] | fc877cf | 2013-07-31 23:08:39 | [diff] [blame] | 68 | DisconnectWindowGtk::DisconnectWindowGtk() |
| 69 | : disconnect_window_(NULL), |
[email protected] | a0117df | 2011-12-03 00:38:41 | [diff] [blame] | 70 | current_width_(0), |
[email protected] | c308f512 | 2013-04-05 03:32:06 | [diff] [blame] | 71 | current_height_(0) { |
[email protected] | 456405f | 2011-06-13 20:42:51 | [diff] [blame] | 72 | } |
| 73 | |
[email protected] | 8b8f0b2 | 2012-05-17 01:58:46 | [diff] [blame] | 74 | DisconnectWindowGtk::~DisconnectWindowGtk() { |
[email protected] | c308f512 | 2013-04-05 03:32:06 | [diff] [blame] | 75 | DCHECK(CalledOnValidThread()); |
| 76 | |
| 77 | if (disconnect_window_) { |
| 78 | gtk_widget_destroy(disconnect_window_); |
| 79 | disconnect_window_ = NULL; |
| 80 | } |
[email protected] | 82d3119 | 2011-07-06 22:16:51 | [diff] [blame] | 81 | } |
| 82 | |
[email protected] | c308f512 | 2013-04-05 03:32:06 | [diff] [blame] | 83 | void DisconnectWindowGtk::Start( |
| 84 | const base::WeakPtr<ClientSessionControl>& client_session_control) { |
| 85 | DCHECK(CalledOnValidThread()); |
[email protected] | 1d06fff | 2013-06-04 00:02:12 | [diff] [blame] | 86 | DCHECK(!client_session_control_.get()); |
| 87 | DCHECK(client_session_control.get()); |
[email protected] | c308f512 | 2013-04-05 03:32:06 | [diff] [blame] | 88 | DCHECK(!disconnect_window_); |
[email protected] | 456405f | 2011-06-13 20:42:51 | [diff] [blame] | 89 | |
[email protected] | c308f512 | 2013-04-05 03:32:06 | [diff] [blame] | 90 | client_session_control_ = client_session_control; |
| 91 | |
| 92 | // Create the window. |
[email protected] | a0117df | 2011-12-03 00:38:41 | [diff] [blame] | 93 | disconnect_window_ = gtk_window_new(GTK_WINDOW_TOPLEVEL); |
[email protected] | 456405f | 2011-06-13 20:42:51 | [diff] [blame] | 94 | GtkWindow* window = GTK_WINDOW(disconnect_window_); |
[email protected] | a0117df | 2011-12-03 00:38:41 | [diff] [blame] | 95 | |
| 96 | g_signal_connect(disconnect_window_, "delete-event", |
| 97 | G_CALLBACK(OnDeleteThunk), this); |
[email protected] | fc877cf | 2013-07-31 23:08:39 | [diff] [blame] | 98 | gtk_window_set_title(window, |
[email protected] | 8bf7178 | 2014-01-27 21:44:11 | [diff] [blame] | 99 | l10n_util::GetStringUTF8(IDS_PRODUCT_NAME).c_str()); |
[email protected] | 456405f | 2011-06-13 20:42:51 | [diff] [blame] | 100 | gtk_window_set_resizable(window, FALSE); |
[email protected] | a0117df | 2011-12-03 00:38:41 | [diff] [blame] | 101 | |
[email protected] | 456405f | 2011-06-13 20:42:51 | [diff] [blame] | 102 | // Try to keep the window always visible. |
| 103 | gtk_window_stick(window); |
| 104 | gtk_window_set_keep_above(window, TRUE); |
[email protected] | a0117df | 2011-12-03 00:38:41 | [diff] [blame] | 105 | |
| 106 | // Remove window titlebar. |
| 107 | gtk_window_set_decorated(window, FALSE); |
| 108 | |
| 109 | // In case the titlebar is still there, try to remove some of the buttons. |
[email protected] | 0e9ea8e8 | 2011-06-16 23:25:20 | [diff] [blame] | 110 | // Utility windows have no minimize button or taskbar presence. |
| 111 | gtk_window_set_type_hint(window, GDK_WINDOW_TYPE_HINT_UTILITY); |
| 112 | gtk_window_set_deletable(window, FALSE); |
[email protected] | 456405f | 2011-06-13 20:42:51 | [diff] [blame] | 113 | |
[email protected] | a0117df | 2011-12-03 00:38:41 | [diff] [blame] | 114 | // Allow custom rendering of the background pixmap. |
| 115 | gtk_widget_set_app_paintable(disconnect_window_, TRUE); |
[email protected] | 456405f | 2011-06-13 20:42:51 | [diff] [blame] | 116 | |
[email protected] | a0117df | 2011-12-03 00:38:41 | [diff] [blame] | 117 | // Handle window resizing, to regenerate the background pixmap and window |
| 118 | // shape bitmap. The stored width & height need to be initialized here |
| 119 | // in case the window is created a second time (the size of the previous |
| 120 | // window would be remembered, preventing the generation of bitmaps for the |
| 121 | // new window). |
| 122 | current_height_ = current_width_ = 0; |
| 123 | g_signal_connect(disconnect_window_, "configure-event", |
| 124 | G_CALLBACK(OnConfigureThunk), this); |
[email protected] | 456405f | 2011-06-13 20:42:51 | [diff] [blame] | 125 | |
[email protected] | a0117df | 2011-12-03 00:38:41 | [diff] [blame] | 126 | // Handle mouse events to allow the user to drag the window around. |
| 127 | gtk_widget_set_events(disconnect_window_, GDK_BUTTON_PRESS_MASK); |
| 128 | g_signal_connect(disconnect_window_, "button-press-event", |
| 129 | G_CALLBACK(OnButtonPressThunk), this); |
| 130 | |
| 131 | // All magic numbers taken from screen shots provided by UX. |
| 132 | // The alignment sets narrow margins at the top and bottom, compared with |
| 133 | // left and right. The left margin is made larger to accommodate the |
| 134 | // window movement gripper. |
| 135 | GtkWidget* align = gtk_alignment_new(0, 0, 1, 1); |
| 136 | gtk_alignment_set_padding(GTK_ALIGNMENT(align), 8, 8, 24, 12); |
| 137 | gtk_container_add(GTK_CONTAINER(window), align); |
| 138 | |
| 139 | GtkWidget* button_row = gtk_hbox_new(FALSE, 12); |
| 140 | gtk_container_add(GTK_CONTAINER(align), button_row); |
| 141 | |
| 142 | button_ = gtk_button_new_with_label( |
[email protected] | 8bf7178 | 2014-01-27 21:44:11 | [diff] [blame] | 143 | l10n_util::GetStringUTF8(IDS_STOP_SHARING_BUTTON).c_str()); |
[email protected] | a0117df | 2011-12-03 00:38:41 | [diff] [blame] | 144 | gtk_box_pack_end(GTK_BOX(button_row), button_, FALSE, FALSE, 0); |
| 145 | |
| 146 | g_signal_connect(button_, "clicked", G_CALLBACK(OnClickedThunk), this); |
[email protected] | 456405f | 2011-06-13 20:42:51 | [diff] [blame] | 147 | |
[email protected] | d600e2d | 2011-08-16 17:45:05 | [diff] [blame] | 148 | message_ = gtk_label_new(NULL); |
[email protected] | a0117df | 2011-12-03 00:38:41 | [diff] [blame] | 149 | gtk_box_pack_end(GTK_BOX(button_row), message_, FALSE, FALSE, 0); |
[email protected] | 456405f | 2011-06-13 20:42:51 | [diff] [blame] | 150 | |
[email protected] | a0117df | 2011-12-03 00:38:41 | [diff] [blame] | 151 | // Override any theme setting for the text color, so that the text is |
| 152 | // readable against the window's background pixmap. |
| 153 | PangoAttrList* attributes = pango_attr_list_new(); |
| 154 | PangoAttribute* text_color = pango_attr_foreground_new(0, 0, 0); |
| 155 | pango_attr_list_insert(attributes, text_color); |
| 156 | gtk_label_set_attributes(GTK_LABEL(message_), attributes); |
[email protected] | dc36559 | 2013-05-02 22:02:19 | [diff] [blame] | 157 | pango_attr_list_unref(attributes); |
[email protected] | a0117df | 2011-12-03 00:38:41 | [diff] [blame] | 158 | |
| 159 | gtk_widget_show_all(disconnect_window_); |
[email protected] | 456405f | 2011-06-13 20:42:51 | [diff] [blame] | 160 | |
[email protected] | c308f512 | 2013-04-05 03:32:06 | [diff] [blame] | 161 | // Extract the user name from the JID. |
| 162 | std::string client_jid = client_session_control_->client_jid(); |
[email protected] | 2c4fcc9 | 2013-12-12 23:47:45 | [diff] [blame] | 163 | base::string16 username = |
[email protected] | 6c3bf03 | 2013-12-25 19:37:03 | [diff] [blame] | 164 | base::UTF8ToUTF16(client_jid.substr(0, client_jid.find('/'))); |
[email protected] | fc877cf | 2013-07-31 23:08:39 | [diff] [blame] | 165 | gtk_label_set_text( |
| 166 | GTK_LABEL(message_), |
[email protected] | 8bf7178 | 2014-01-27 21:44:11 | [diff] [blame] | 167 | l10n_util::GetStringFUTF8(IDS_MESSAGE_SHARED, username).c_str()); |
[email protected] | c308f512 | 2013-04-05 03:32:06 | [diff] [blame] | 168 | gtk_window_present(window); |
[email protected] | 456405f | 2011-06-13 20:42:51 | [diff] [blame] | 169 | } |
| 170 | |
[email protected] | b8c35dd9b | 2014-04-16 02:54:17 | [diff] [blame] | 171 | void DisconnectWindowGtk::OnClicked(GtkButton* button) { |
[email protected] | c308f512 | 2013-04-05 03:32:06 | [diff] [blame] | 172 | DCHECK(CalledOnValidThread()); |
| 173 | |
[email protected] | 1d06fff | 2013-06-04 00:02:12 | [diff] [blame] | 174 | if (client_session_control_.get()) |
[email protected] | c308f512 | 2013-04-05 03:32:06 | [diff] [blame] | 175 | client_session_control_->DisconnectSession(); |
[email protected] | f41095f8 | 2011-06-07 16:57:44 | [diff] [blame] | 176 | } |
| 177 | |
[email protected] | c308f512 | 2013-04-05 03:32:06 | [diff] [blame] | 178 | gboolean DisconnectWindowGtk::OnDelete(GtkWidget* window, |
| 179 | GdkEvent* event) { |
| 180 | DCHECK(CalledOnValidThread()); |
[email protected] | a0117df | 2011-12-03 00:38:41 | [diff] [blame] | 181 | |
[email protected] | 1d06fff | 2013-06-04 00:02:12 | [diff] [blame] | 182 | if (client_session_control_.get()) |
[email protected] | c308f512 | 2013-04-05 03:32:06 | [diff] [blame] | 183 | client_session_control_->DisconnectSession(); |
[email protected] | a0117df | 2011-12-03 00:38:41 | [diff] [blame] | 184 | return TRUE; |
| 185 | } |
| 186 | |
[email protected] | 8b8f0b2 | 2012-05-17 01:58:46 | [diff] [blame] | 187 | gboolean DisconnectWindowGtk::OnConfigure(GtkWidget* widget, |
[email protected] | c308f512 | 2013-04-05 03:32:06 | [diff] [blame] | 188 | GdkEventConfigure* event) { |
| 189 | DCHECK(CalledOnValidThread()); |
| 190 | |
[email protected] | a0117df | 2011-12-03 00:38:41 | [diff] [blame] | 191 | // Only generate bitmaps if the size has actually changed. |
| 192 | if (event->width == current_width_ && event->height == current_height_) |
| 193 | return FALSE; |
| 194 | |
| 195 | current_width_ = event->width; |
| 196 | current_height_ = event->height; |
| 197 | |
| 198 | // Create the depth 1 pixmap for the window shape. |
| 199 | GdkPixmap* shape_mask = gdk_pixmap_new(NULL, current_width_, current_height_, |
| 200 | 1); |
| 201 | cairo_t* cairo_context = gdk_cairo_create(shape_mask); |
| 202 | |
| 203 | // Set the arc radius for the corners. |
| 204 | const int kCornerRadius = 6; |
| 205 | |
| 206 | // Initialize the whole bitmap to be transparent. |
| 207 | cairo_set_source_rgba(cairo_context, 0, 0, 0, 0); |
| 208 | cairo_set_operator(cairo_context, CAIRO_OPERATOR_SOURCE); |
| 209 | cairo_paint(cairo_context); |
| 210 | |
| 211 | // Paint an opaque round rect covering the whole area (leaving the extreme |
| 212 | // corners transparent). |
| 213 | cairo_set_source_rgba(cairo_context, 1, 1, 1, 1); |
| 214 | cairo_set_operator(cairo_context, CAIRO_OPERATOR_SOURCE); |
| 215 | AddRoundRectPath(cairo_context, current_width_, current_height_, |
| 216 | kCornerRadius); |
| 217 | cairo_fill(cairo_context); |
| 218 | |
| 219 | cairo_destroy(cairo_context); |
| 220 | gdk_window_shape_combine_mask(widget->window, shape_mask, 0, 0); |
| 221 | g_object_unref(shape_mask); |
| 222 | |
| 223 | // Create a full-color pixmap for the window background image. |
| 224 | GdkPixmap* background = gdk_pixmap_new(NULL, current_width_, current_height_, |
| 225 | 24); |
| 226 | cairo_context = gdk_cairo_create(background); |
| 227 | |
| 228 | // Paint the whole bitmap one color. |
| 229 | cairo_set_source_rgb(cairo_context, 0.91, 0.91, 0.91); |
| 230 | cairo_paint(cairo_context); |
| 231 | |
| 232 | // Paint the round-rectangle edge. |
| 233 | cairo_set_source_rgb(cairo_context, 0.13, 0.69, 0.11); |
| 234 | cairo_set_line_width(cairo_context, 6); |
| 235 | AddRoundRectPath(cairo_context, current_width_, current_height_, |
| 236 | kCornerRadius); |
| 237 | cairo_stroke(cairo_context); |
| 238 | |
| 239 | // Render the window-gripper. In order for a straight line to light up |
| 240 | // single pixels, Cairo requires the coordinates to have fractional |
| 241 | // components of 0.5 (so the "/ 2" is a deliberate integer division). |
| 242 | double gripper_top = current_height_ / 2 - 10.5; |
| 243 | double gripper_bottom = current_height_ / 2 + 10.5; |
| 244 | cairo_set_line_width(cairo_context, 1); |
| 245 | |
| 246 | double x = 12.5; |
| 247 | cairo_set_source_rgb(cairo_context, 0.70, 0.70, 0.70); |
| 248 | cairo_move_to(cairo_context, x, gripper_top); |
| 249 | cairo_line_to(cairo_context, x, gripper_bottom); |
| 250 | cairo_stroke(cairo_context); |
| 251 | x += 3; |
| 252 | cairo_move_to(cairo_context, x, gripper_top); |
| 253 | cairo_line_to(cairo_context, x, gripper_bottom); |
| 254 | cairo_stroke(cairo_context); |
| 255 | |
| 256 | x -= 2; |
| 257 | cairo_set_source_rgb(cairo_context, 0.97, 0.97, 0.97); |
| 258 | cairo_move_to(cairo_context, x, gripper_top); |
| 259 | cairo_line_to(cairo_context, x, gripper_bottom); |
| 260 | cairo_stroke(cairo_context); |
| 261 | x += 3; |
| 262 | cairo_move_to(cairo_context, x, gripper_top); |
| 263 | cairo_line_to(cairo_context, x, gripper_bottom); |
| 264 | cairo_stroke(cairo_context); |
| 265 | |
| 266 | cairo_destroy(cairo_context); |
| 267 | |
| 268 | gdk_window_set_back_pixmap(widget->window, background, FALSE); |
| 269 | g_object_unref(background); |
| 270 | gdk_window_invalidate_rect(widget->window, NULL, TRUE); |
| 271 | |
| 272 | return FALSE; |
| 273 | } |
| 274 | |
[email protected] | 8b8f0b2 | 2012-05-17 01:58:46 | [diff] [blame] | 275 | gboolean DisconnectWindowGtk::OnButtonPress(GtkWidget* widget, |
[email protected] | c308f512 | 2013-04-05 03:32:06 | [diff] [blame] | 276 | GdkEventButton* event) { |
| 277 | DCHECK(CalledOnValidThread()); |
| 278 | |
[email protected] | a0117df | 2011-12-03 00:38:41 | [diff] [blame] | 279 | gtk_window_begin_move_drag(GTK_WINDOW(disconnect_window_), |
| 280 | event->button, |
| 281 | event->x_root, |
| 282 | event->y_root, |
| 283 | event->time); |
| 284 | return FALSE; |
| 285 | } |
| 286 | |
[email protected] | c308f512 | 2013-04-05 03:32:06 | [diff] [blame] | 287 | } // namespace |
| 288 | |
| 289 | // static |
[email protected] | fc877cf | 2013-07-31 23:08:39 | [diff] [blame] | 290 | scoped_ptr<HostWindow> HostWindow::CreateDisconnectWindow() { |
sergeyu | 2d69088 | 2014-10-01 02:36:43 | [diff] [blame] | 291 | return make_scoped_ptr(new DisconnectWindowGtk()); |
[email protected] | f41095f8 | 2011-06-07 16:57:44 | [diff] [blame] | 292 | } |
[email protected] | edd125d8 | 2011-06-28 22:47:36 | [diff] [blame] | 293 | |
| 294 | } // namespace remoting |