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_); |
Jamie
2011/11/30 02:48:56
Do you ever not want to see disconnect_window_ as
Lambros
2011/12/02 01:41:55
The Chrome examples I saw elsewhere used GtkWidget
| |
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", |
Jamie
2011/11/30 02:48:56
...For example, will this still compile if disconn
Lambros
2011/12/02 01:41:55
That particular line would still compile (g_signal
| |
69 G_CALLBACK(OnDeleteThunk), this); | |
70 gtk_window_set_title(window, UTF16ToUTF8(ui_strings.product_name).c_str()); | |
Jamie
2011/11/30 02:48:56
What's the purpose of setting the title?
Lambros
2011/12/02 01:41:55
Might as well keep it. Theoretically, a user migh
| |
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; | |
Jamie
2011/11/30 02:48:56
I don't think this is necessary, as we regenerate
Lambros
2011/12/02 01:41:55
Actually, we were leaking the bitmaps! I've fixed
| |
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. | |
Jamie
2011/11/30 02:48:56
Will this work for bidi languages? If not, then th
Lambros
2011/12/02 01:41:55
Haven't tried it. I expect GTK will display the c
| |
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* cr, int width, int height, int radius) { | |
171 cairo_new_sub_path(cr); | |
172 cairo_arc(cr, width - radius, radius, radius, -M_PI_2, 0); | |
173 cairo_arc(cr, width - radius, height - radius, radius, 0, M_PI_2); | |
174 cairo_arc(cr, radius, height - radius, radius, M_PI_2, 2 * M_PI_2); | |
175 cairo_arc(cr, radius, radius, radius, 2 * M_PI_2, 3 * M_PI_2); | |
176 cairo_close_path(cr); | |
177 } | |
178 | |
179 } // namespace | |
180 | |
181 gboolean DisconnectWindowLinux::OnConfigure(GtkWidget* widget, | |
182 GdkEventConfigure* event) { | |
183 // Only generate bitmaps if the size has actually changed. | |
184 if (event->width == current_width_ && event->height == current_height_) | |
185 return FALSE; | |
186 | |
187 current_width_ = event->width; | |
188 current_height_ = event->height; | |
189 | |
190 // Create the depth 1 pixmap for the window shape. | |
191 GdkPixmap* shape_mask = gdk_pixmap_new(NULL, current_width_, current_height_, | |
Jamie
2011/11/30 02:48:56
I assume that cairo_destroy also destroys this pix
Lambros
2011/12/02 01:41:55
I'm pretty sure cairo_destroy() doesn't destroy th
| |
192 1); | |
193 cairo_t* cr = gdk_cairo_create(shape_mask); | |
Jamie
2011/11/30 02:48:56
Can this have a more descriptive name, please?
Lambros
2011/12/02 01:41:55
Done (groan!).
Jamie
2011/12/02 02:23:41
I think |context| would be sufficient if you think
| |
194 | |
195 // Set the arc radius for the corners. It is theoretically possible that a | |
196 // user might be able to resize the window (the Window Manager might not | |
197 // honor the fixed-size request), so make allowances for extremely thin | |
198 // sizes just in case. | |
Jamie
2011/11/30 02:48:56
What happens if we don't? If it just looks crappy
Lambros
2011/12/02 01:41:55
Yeah, it just looks very slightly strange at the c
| |
199 int radius = std::min(6, std::min(current_width_ / 2, current_height_ / 2)); | |
200 | |
201 // Initialize the whole bitmap to be transparent. | |
202 cairo_set_source_rgba(cr, 0, 0, 0, 0); | |
203 cairo_set_operator(cr, CAIRO_OPERATOR_SOURCE); | |
204 cairo_paint(cr); | |
205 | |
206 // Paint an opaque round rect covering the whole area (leaving the extreme | |
207 // corners transparent). | |
208 cairo_set_source_rgba(cr, 1, 1, 1, 1); | |
209 cairo_set_operator(cr, CAIRO_OPERATOR_SOURCE); | |
210 AddRoundRectPath(cr, current_width_, current_height_, radius); | |
211 cairo_fill(cr); | |
212 | |
213 cairo_destroy(cr); | |
214 gdk_window_shape_combine_mask(widget->window, shape_mask, 0, 0); | |
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 cr = gdk_cairo_create(background); | |
220 | |
221 // Paint the whole bitmap one color. | |
222 cairo_set_source_rgb(cr, 233 / 256.0, 233 / 256.0, 233 / 256.0); | |
Jamie
2011/11/30 02:48:56
Seems an odd way of specifying the colour. I think
Lambros
2011/12/02 01:41:55
Done.
| |
223 cairo_paint(cr); | |
224 | |
225 // Paint the round-rectangle edge. | |
226 cairo_set_source_rgb(cr, 32 / 256.0, 176 / 256.0, 29 / 256.0); | |
227 cairo_set_line_width(cr, 6); | |
228 AddRoundRectPath(cr, current_width_, current_height_, radius); | |
229 cairo_stroke(cr); | |
230 | |
231 // Render the window-gripper. In order for a straight line to light up | |
232 // single pixels, Cairo requires the coordinates to have fractional | |
233 // components of 0.5 (so the "/ 2" is a deliberate integer division). | |
234 double gripper_top = current_height_ / 2 - 10.5; | |
235 int gripper_bottom = current_height_ / 2 + 10.5; | |
Jamie
2011/11/30 02:48:56
I don't understand this. Why is one of these a dou
Lambros
2011/12/02 01:41:55
Doh! They should both be doubles. Fixed.
| |
236 cairo_set_line_width(cr, 1); | |
237 | |
238 double x = 12.5; | |
239 cairo_set_source_rgb(cr, 194 / 256.0, 194 / 256.0, 194 / 256.0); | |
Jamie
2011/11/30 02:48:56
Seems an odd way of specifying the colour. I think
Lambros
2011/12/02 01:41:55
Done.
| |
240 cairo_move_to(cr, x, gripper_top); | |
241 cairo_line_to(cr, x, gripper_bottom); | |
242 cairo_stroke(cr); | |
243 x += 3; | |
244 cairo_move_to(cr, x, gripper_top); | |
245 cairo_line_to(cr, x, gripper_bottom); | |
246 cairo_stroke(cr); | |
247 | |
248 x -= 2; | |
249 cairo_set_source_rgb(cr, 248 / 256.0, 248 / 256.0, 248 / 256.0); | |
250 cairo_move_to(cr, x, gripper_top); | |
251 cairo_line_to(cr, x, gripper_bottom); | |
252 cairo_stroke(cr); | |
253 x += 3; | |
254 cairo_move_to(cr, x, gripper_top); | |
255 cairo_line_to(cr, x, gripper_bottom); | |
256 cairo_stroke(cr); | |
257 | |
258 cairo_destroy(cr); | |
259 | |
260 gdk_window_set_back_pixmap(widget->window, background, FALSE); | |
261 gdk_window_invalidate_rect(widget->window, NULL, TRUE); | |
262 | |
263 return FALSE; | |
264 } | |
265 | |
266 gboolean DisconnectWindowLinux::OnButtonPress(GtkWidget* widget, | |
267 GdkEventButton* event) { | |
268 gtk_window_begin_move_drag(GTK_WINDOW(disconnect_window_), | |
269 event->button, | |
270 event->x_root, | |
271 event->y_root, | |
272 event->time); | |
273 return FALSE; | |
274 } | |
275 | |
127 DisconnectWindow* DisconnectWindow::Create() { | 276 DisconnectWindow* DisconnectWindow::Create() { |
128 return new DisconnectWindowLinux; | 277 return new DisconnectWindowLinux; |
129 } | 278 } |
130 | 279 |
131 } // namespace remoting | 280 } // namespace remoting |
OLD | NEW |