OLD | NEW |
1 // Copyright (c) 2011 The Chromium Authors. All rights reserved. | 1 // Copyright (c) 2011 The Chromium Authors. All rights reserved. |
2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | 4 |
5 #include "remoting/host/disconnect_window.h" | 5 #include "remoting/host/disconnect_window.h" |
6 | 6 |
7 #include <gtk/gtk.h> | 7 #include <gtk/gtk.h> |
| 8 #include <math.h> |
8 | 9 |
9 #include "base/compiler_specific.h" | 10 #include "base/compiler_specific.h" |
10 #include "base/logging.h" | 11 #include "base/logging.h" |
11 #include "base/string_util.h" | 12 #include "base/string_util.h" |
12 #include "base/utf_string_conversions.h" | 13 #include "base/utf_string_conversions.h" |
13 #include "remoting/host/chromoting_host.h" | 14 #include "remoting/host/chromoting_host.h" |
14 #include "remoting/host/ui_strings.h" | 15 #include "remoting/host/ui_strings.h" |
15 #include "ui/base/gtk/gtk_signal.h" | 16 #include "ui/base/gtk/gtk_signal.h" |
16 | 17 |
17 namespace { | |
18 // The width in pixels at which the message will wrap. Given that the message | |
19 // contains an un-splittable email address, it's unlikely that a fixed width | |
20 // is going to look aesthetically pleasing in all languages. | |
21 // TODO(jamiewalch): Replace this with a layout that only uses a single line, | |
22 // and which is docked at the top or bottom of the host screen, as in our | |
23 // UI mocks. | |
24 const int kMessageWidth = 300; | |
25 } | |
26 | |
27 namespace remoting { | 18 namespace remoting { |
28 | 19 |
29 class DisconnectWindowLinux : public DisconnectWindow { | 20 class DisconnectWindowLinux : public DisconnectWindow { |
30 public: | 21 public: |
31 DisconnectWindowLinux(); | 22 DisconnectWindowLinux(); |
32 virtual ~DisconnectWindowLinux(); | 23 virtual ~DisconnectWindowLinux(); |
33 | 24 |
34 virtual void Show(ChromotingHost* host, | 25 virtual void Show(ChromotingHost* host, |
35 const std::string& username) OVERRIDE; | 26 const std::string& username) OVERRIDE; |
36 virtual void Hide() OVERRIDE; | 27 virtual void Hide() OVERRIDE; |
37 | 28 |
38 private: | 29 private: |
39 CHROMEGTK_CALLBACK_1(DisconnectWindowLinux, void, OnResponse, int); | 30 CHROMEGTK_CALLBACK_1(DisconnectWindowLinux, gboolean, OnDelete, GdkEvent*); |
| 31 CHROMEGTK_CALLBACK_0(DisconnectWindowLinux, void, OnClicked); |
| 32 CHROMEGTK_CALLBACK_1(DisconnectWindowLinux, gboolean, OnConfigure, |
| 33 GdkEventConfigure*); |
| 34 CHROMEGTK_CALLBACK_1(DisconnectWindowLinux, gboolean, OnButtonPress, |
| 35 GdkEventButton*); |
40 | 36 |
41 void CreateWindow(const UiStrings& ui_strings); | 37 void CreateWindow(const UiStrings& ui_strings); |
42 | 38 |
43 ChromotingHost* host_; | 39 ChromotingHost* host_; |
44 GtkWidget* disconnect_window_; | 40 GtkWidget* disconnect_window_; |
45 GtkWidget* message_; | 41 GtkWidget* message_; |
| 42 GtkWidget* button_; |
| 43 |
| 44 // Used to distinguish resize events from other types of "configure-event" |
| 45 // notifications. |
| 46 int current_width_; |
| 47 int current_height_; |
46 | 48 |
47 DISALLOW_COPY_AND_ASSIGN(DisconnectWindowLinux); | 49 DISALLOW_COPY_AND_ASSIGN(DisconnectWindowLinux); |
48 }; | 50 }; |
49 | 51 |
50 DisconnectWindowLinux::DisconnectWindowLinux() | 52 DisconnectWindowLinux::DisconnectWindowLinux() |
51 : host_(NULL), | 53 : host_(NULL), |
52 disconnect_window_(NULL) { | 54 disconnect_window_(NULL), |
| 55 current_width_(0), |
| 56 current_height_(0) { |
53 } | 57 } |
54 | 58 |
55 DisconnectWindowLinux::~DisconnectWindowLinux() { | 59 DisconnectWindowLinux::~DisconnectWindowLinux() { |
56 } | 60 } |
57 | 61 |
58 void DisconnectWindowLinux::CreateWindow(const UiStrings& ui_strings) { | 62 void DisconnectWindowLinux::CreateWindow(const UiStrings& ui_strings) { |
59 if (disconnect_window_) return; | 63 if (disconnect_window_) return; |
60 | 64 |
61 disconnect_window_ = gtk_dialog_new_with_buttons( | 65 disconnect_window_ = gtk_window_new(GTK_WINDOW_TOPLEVEL); |
62 UTF16ToUTF8(ui_strings.product_name).c_str(), | 66 GtkWindow* window = GTK_WINDOW(disconnect_window_); |
63 NULL, | |
64 GTK_DIALOG_NO_SEPARATOR, | |
65 UTF16ToUTF8(ui_strings.disconnect_button_text_plus_shortcut).c_str(), | |
66 GTK_RESPONSE_OK, | |
67 NULL); | |
68 | 67 |
69 GtkWindow* window = GTK_WINDOW(disconnect_window_); | 68 g_signal_connect(disconnect_window_, "delete-event", |
| 69 G_CALLBACK(OnDeleteThunk), this); |
| 70 gtk_window_set_title(window, UTF16ToUTF8(ui_strings.product_name).c_str()); |
70 gtk_window_set_resizable(window, FALSE); | 71 gtk_window_set_resizable(window, FALSE); |
| 72 |
71 // Try to keep the window always visible. | 73 // Try to keep the window always visible. |
72 gtk_window_stick(window); | 74 gtk_window_stick(window); |
73 gtk_window_set_keep_above(window, TRUE); | 75 gtk_window_set_keep_above(window, TRUE); |
| 76 |
| 77 // Remove window titlebar. |
| 78 gtk_window_set_decorated(window, FALSE); |
| 79 |
| 80 // In case the titlebar is still there, try to remove some of the buttons. |
74 // Utility windows have no minimize button or taskbar presence. | 81 // Utility windows have no minimize button or taskbar presence. |
75 gtk_window_set_type_hint(window, GDK_WINDOW_TYPE_HINT_UTILITY); | 82 gtk_window_set_type_hint(window, GDK_WINDOW_TYPE_HINT_UTILITY); |
76 gtk_window_set_deletable(window, FALSE); | 83 gtk_window_set_deletable(window, FALSE); |
77 | 84 |
78 g_signal_connect(disconnect_window_, "response", | 85 // Allow custom rendering of the background pixmap. |
79 G_CALLBACK(OnResponseThunk), this); | 86 gtk_widget_set_app_paintable(disconnect_window_, TRUE); |
80 | 87 |
81 GtkWidget* content_area = | 88 // Handle window resizing, to regenerate the background pixmap and window |
82 gtk_dialog_get_content_area(GTK_DIALOG(disconnect_window_)); | 89 // shape bitmap. The stored width & height need to be initialized here |
| 90 // in case the window is created a second time (the size of the previous |
| 91 // window would be remembered, preventing the generation of bitmaps for the |
| 92 // new window). |
| 93 current_height_ = current_width_ = 0; |
| 94 g_signal_connect(disconnect_window_, "configure-event", |
| 95 G_CALLBACK(OnConfigureThunk), this); |
83 | 96 |
84 GtkWidget* message_row = gtk_hbox_new(FALSE, 0); | 97 // Handle mouse events to allow the user to drag the window around. |
85 // TODO(lambroslambrou): Replace the magic number with an appropriate | 98 gtk_widget_set_events(disconnect_window_, GDK_BUTTON_PRESS_MASK); |
86 // constant from a header file (such as chrome/browser/ui/gtk/gtk_util.h | 99 g_signal_connect(disconnect_window_, "button-press-event", |
87 // but check_deps disallows its #inclusion here). | 100 G_CALLBACK(OnButtonPressThunk), this); |
88 gtk_container_set_border_width(GTK_CONTAINER(message_row), 12); | 101 |
89 gtk_container_add(GTK_CONTAINER(content_area), message_row); | 102 // All magic numbers taken from screen shots provided by UX. |
| 103 // The alignment sets narrow margins at the top and bottom, compared with |
| 104 // left and right. The left margin is made larger to accommodate the |
| 105 // window movement gripper. |
| 106 GtkWidget* align = gtk_alignment_new(0, 0, 1, 1); |
| 107 gtk_alignment_set_padding(GTK_ALIGNMENT(align), 8, 8, 24, 12); |
| 108 gtk_container_add(GTK_CONTAINER(window), align); |
| 109 |
| 110 GtkWidget* button_row = gtk_hbox_new(FALSE, 12); |
| 111 gtk_container_add(GTK_CONTAINER(align), button_row); |
| 112 |
| 113 button_ = gtk_button_new_with_label( |
| 114 UTF16ToUTF8(ui_strings.disconnect_button_text_plus_shortcut).c_str()); |
| 115 gtk_box_pack_end(GTK_BOX(button_row), button_, FALSE, FALSE, 0); |
| 116 |
| 117 g_signal_connect(button_, "clicked", G_CALLBACK(OnClickedThunk), this); |
90 | 118 |
91 message_ = gtk_label_new(NULL); | 119 message_ = gtk_label_new(NULL); |
92 gtk_widget_set_size_request(message_, kMessageWidth, -1); | 120 gtk_box_pack_end(GTK_BOX(button_row), message_, FALSE, FALSE, 0); |
93 gtk_label_set_line_wrap(GTK_LABEL(message_), true); | |
94 gtk_container_add(GTK_CONTAINER(message_row), message_); | |
95 | 121 |
96 gtk_widget_show_all(content_area); | 122 // Override any theme setting for the text color, so that the text is |
| 123 // readable against the window's background pixmap. |
| 124 PangoAttrList* attributes = pango_attr_list_new(); |
| 125 PangoAttribute* text_color = pango_attr_foreground_new(0, 0, 0); |
| 126 pango_attr_list_insert(attributes, text_color); |
| 127 gtk_label_set_attributes(GTK_LABEL(message_), attributes); |
| 128 |
| 129 gtk_widget_show_all(disconnect_window_); |
97 } | 130 } |
98 | 131 |
99 void DisconnectWindowLinux::Show(ChromotingHost* host, | 132 void DisconnectWindowLinux::Show(ChromotingHost* host, |
100 const std::string& username) { | 133 const std::string& username) { |
101 host_ = host; | 134 host_ = host; |
102 CreateWindow(host->ui_strings()); | 135 CreateWindow(host->ui_strings()); |
103 | 136 |
104 string16 text = ReplaceStringPlaceholders( | 137 string16 text = ReplaceStringPlaceholders( |
105 host->ui_strings().disconnect_message, UTF8ToUTF16(username), NULL); | 138 host->ui_strings().disconnect_message, UTF8ToUTF16(username), NULL); |
106 gtk_label_set_text(GTK_LABEL(message_), UTF16ToUTF8(text).c_str()); | 139 gtk_label_set_text(GTK_LABEL(message_), UTF16ToUTF8(text).c_str()); |
107 gtk_window_present(GTK_WINDOW(disconnect_window_)); | 140 gtk_window_present(GTK_WINDOW(disconnect_window_)); |
108 } | 141 } |
109 | 142 |
110 void DisconnectWindowLinux::Hide() { | 143 void DisconnectWindowLinux::Hide() { |
111 if (disconnect_window_) { | 144 if (disconnect_window_) { |
112 gtk_widget_destroy(disconnect_window_); | 145 gtk_widget_destroy(disconnect_window_); |
113 disconnect_window_ = NULL; | 146 disconnect_window_ = NULL; |
114 } | 147 } |
115 } | 148 } |
116 | 149 |
117 void DisconnectWindowLinux::OnResponse(GtkWidget* dialog, int response_id) { | 150 void DisconnectWindowLinux::OnClicked(GtkWidget* button) { |
118 // |response_id| is ignored, because there is only one button, and pressing | |
119 // it should have the same effect as closing the window (if the window Close | |
120 // button were visible). | |
121 CHECK(host_); | 151 CHECK(host_); |
122 | 152 |
123 host_->Shutdown(base::Closure()); | 153 host_->Shutdown(base::Closure()); |
124 Hide(); | 154 Hide(); |
125 } | 155 } |
126 | 156 |
| 157 gboolean DisconnectWindowLinux::OnDelete(GtkWidget* window, GdkEvent* event) { |
| 158 CHECK(host_); |
| 159 |
| 160 host_->Shutdown(base::Closure()); |
| 161 Hide(); |
| 162 |
| 163 return TRUE; |
| 164 } |
| 165 |
| 166 namespace { |
| 167 // Helper function for creating a rectangular path with rounded corners, as |
| 168 // Cairo doesn't have this facility. |radius| is the arc-radius of each |
| 169 // corner. The bounding rectangle extends from (0, 0) to (width, height). |
| 170 void AddRoundRectPath(cairo_t* cairo_context, int width, int height, |
| 171 int radius) { |
| 172 cairo_new_sub_path(cairo_context); |
| 173 cairo_arc(cairo_context, width - radius, radius, radius, -M_PI_2, 0); |
| 174 cairo_arc(cairo_context, width - radius, height - radius, radius, 0, M_PI_2); |
| 175 cairo_arc(cairo_context, radius, height - radius, radius, M_PI_2, 2 * M_PI_2); |
| 176 cairo_arc(cairo_context, radius, radius, radius, 2 * M_PI_2, 3 * M_PI_2); |
| 177 cairo_close_path(cairo_context); |
| 178 } |
| 179 |
| 180 } // namespace |
| 181 |
| 182 gboolean DisconnectWindowLinux::OnConfigure(GtkWidget* widget, |
| 183 GdkEventConfigure* event) { |
| 184 // Only generate bitmaps if the size has actually changed. |
| 185 if (event->width == current_width_ && event->height == current_height_) |
| 186 return FALSE; |
| 187 |
| 188 current_width_ = event->width; |
| 189 current_height_ = event->height; |
| 190 |
| 191 // Create the depth 1 pixmap for the window shape. |
| 192 GdkPixmap* shape_mask = gdk_pixmap_new(NULL, current_width_, current_height_, |
| 193 1); |
| 194 cairo_t* cairo_context = gdk_cairo_create(shape_mask); |
| 195 |
| 196 // Set the arc radius for the corners. |
| 197 const int kCornerRadius = 6; |
| 198 |
| 199 // Initialize the whole bitmap to be transparent. |
| 200 cairo_set_source_rgba(cairo_context, 0, 0, 0, 0); |
| 201 cairo_set_operator(cairo_context, CAIRO_OPERATOR_SOURCE); |
| 202 cairo_paint(cairo_context); |
| 203 |
| 204 // Paint an opaque round rect covering the whole area (leaving the extreme |
| 205 // corners transparent). |
| 206 cairo_set_source_rgba(cairo_context, 1, 1, 1, 1); |
| 207 cairo_set_operator(cairo_context, CAIRO_OPERATOR_SOURCE); |
| 208 AddRoundRectPath(cairo_context, current_width_, current_height_, |
| 209 kCornerRadius); |
| 210 cairo_fill(cairo_context); |
| 211 |
| 212 cairo_destroy(cairo_context); |
| 213 gdk_window_shape_combine_mask(widget->window, shape_mask, 0, 0); |
| 214 g_object_unref(shape_mask); |
| 215 |
| 216 // Create a full-color pixmap for the window background image. |
| 217 GdkPixmap* background = gdk_pixmap_new(NULL, current_width_, current_height_, |
| 218 24); |
| 219 cairo_context = gdk_cairo_create(background); |
| 220 |
| 221 // Paint the whole bitmap one color. |
| 222 cairo_set_source_rgb(cairo_context, 0.91, 0.91, 0.91); |
| 223 cairo_paint(cairo_context); |
| 224 |
| 225 // Paint the round-rectangle edge. |
| 226 cairo_set_source_rgb(cairo_context, 0.13, 0.69, 0.11); |
| 227 cairo_set_line_width(cairo_context, 6); |
| 228 AddRoundRectPath(cairo_context, current_width_, current_height_, |
| 229 kCornerRadius); |
| 230 cairo_stroke(cairo_context); |
| 231 |
| 232 // Render the window-gripper. In order for a straight line to light up |
| 233 // single pixels, Cairo requires the coordinates to have fractional |
| 234 // components of 0.5 (so the "/ 2" is a deliberate integer division). |
| 235 double gripper_top = current_height_ / 2 - 10.5; |
| 236 double gripper_bottom = current_height_ / 2 + 10.5; |
| 237 cairo_set_line_width(cairo_context, 1); |
| 238 |
| 239 double x = 12.5; |
| 240 cairo_set_source_rgb(cairo_context, 0.70, 0.70, 0.70); |
| 241 cairo_move_to(cairo_context, x, gripper_top); |
| 242 cairo_line_to(cairo_context, x, gripper_bottom); |
| 243 cairo_stroke(cairo_context); |
| 244 x += 3; |
| 245 cairo_move_to(cairo_context, x, gripper_top); |
| 246 cairo_line_to(cairo_context, x, gripper_bottom); |
| 247 cairo_stroke(cairo_context); |
| 248 |
| 249 x -= 2; |
| 250 cairo_set_source_rgb(cairo_context, 0.97, 0.97, 0.97); |
| 251 cairo_move_to(cairo_context, x, gripper_top); |
| 252 cairo_line_to(cairo_context, x, gripper_bottom); |
| 253 cairo_stroke(cairo_context); |
| 254 x += 3; |
| 255 cairo_move_to(cairo_context, x, gripper_top); |
| 256 cairo_line_to(cairo_context, x, gripper_bottom); |
| 257 cairo_stroke(cairo_context); |
| 258 |
| 259 cairo_destroy(cairo_context); |
| 260 |
| 261 gdk_window_set_back_pixmap(widget->window, background, FALSE); |
| 262 g_object_unref(background); |
| 263 gdk_window_invalidate_rect(widget->window, NULL, TRUE); |
| 264 |
| 265 return FALSE; |
| 266 } |
| 267 |
| 268 gboolean DisconnectWindowLinux::OnButtonPress(GtkWidget* widget, |
| 269 GdkEventButton* event) { |
| 270 gtk_window_begin_move_drag(GTK_WINDOW(disconnect_window_), |
| 271 event->button, |
| 272 event->x_root, |
| 273 event->y_root, |
| 274 event->time); |
| 275 return FALSE; |
| 276 } |
| 277 |
127 DisconnectWindow* DisconnectWindow::Create() { | 278 DisconnectWindow* DisconnectWindow::Create() { |
128 return new DisconnectWindowLinux; | 279 return new DisconnectWindowLinux; |
129 } | 280 } |
130 | 281 |
131 } // namespace remoting | 282 } // namespace remoting |
OLD | NEW |