OLD | NEW |
---|---|
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. | 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 | 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 "ui/views/widget/desktop_aura/desktop_window_tree_host_x11.h" | 5 #include "ui/views/widget/desktop_aura/desktop_window_tree_host_x11.h" |
6 | 6 |
7 #include <X11/extensions/shape.h> | 7 #include <X11/extensions/shape.h> |
8 #include <X11/extensions/XInput2.h> | 8 #include <X11/extensions/XInput2.h> |
9 #include <X11/Xatom.h> | 9 #include <X11/Xatom.h> |
10 #include <X11/Xregion.h> | 10 #include <X11/Xregion.h> |
(...skipping 59 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
70 | 70 |
71 // Constants that are part of EWMH. | 71 // Constants that are part of EWMH. |
72 const int k_NET_WM_STATE_ADD = 1; | 72 const int k_NET_WM_STATE_ADD = 1; |
73 const int k_NET_WM_STATE_REMOVE = 0; | 73 const int k_NET_WM_STATE_REMOVE = 0; |
74 | 74 |
75 const char* kAtomsToCache[] = { | 75 const char* kAtomsToCache[] = { |
76 "UTF8_STRING", | 76 "UTF8_STRING", |
77 "WM_DELETE_WINDOW", | 77 "WM_DELETE_WINDOW", |
78 "WM_PROTOCOLS", | 78 "WM_PROTOCOLS", |
79 "WM_S0", | 79 "WM_S0", |
80 "_NET_FRAME_EXTENTS", | |
80 "_NET_WM_CM_S0", | 81 "_NET_WM_CM_S0", |
81 "_NET_WM_ICON", | 82 "_NET_WM_ICON", |
82 "_NET_WM_NAME", | 83 "_NET_WM_NAME", |
83 "_NET_WM_PID", | 84 "_NET_WM_PID", |
84 "_NET_WM_PING", | 85 "_NET_WM_PING", |
85 "_NET_WM_STATE", | 86 "_NET_WM_STATE", |
86 "_NET_WM_STATE_ABOVE", | 87 "_NET_WM_STATE_ABOVE", |
87 "_NET_WM_STATE_FULLSCREEN", | 88 "_NET_WM_STATE_FULLSCREEN", |
88 "_NET_WM_STATE_HIDDEN", | 89 "_NET_WM_STATE_HIDDEN", |
89 "_NET_WM_STATE_MAXIMIZED_HORZ", | 90 "_NET_WM_STATE_MAXIMIZED_HORZ", |
(...skipping 44 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
134 is_fullscreen_(false), | 135 is_fullscreen_(false), |
135 is_always_on_top_(false), | 136 is_always_on_top_(false), |
136 use_native_frame_(false), | 137 use_native_frame_(false), |
137 use_argb_visual_(false), | 138 use_argb_visual_(false), |
138 drag_drop_client_(NULL), | 139 drag_drop_client_(NULL), |
139 current_cursor_(ui::kCursorNull), | 140 current_cursor_(ui::kCursorNull), |
140 native_widget_delegate_(native_widget_delegate), | 141 native_widget_delegate_(native_widget_delegate), |
141 desktop_native_widget_aura_(desktop_native_widget_aura), | 142 desktop_native_widget_aura_(desktop_native_widget_aura), |
142 content_window_(NULL), | 143 content_window_(NULL), |
143 window_parent_(NULL), | 144 window_parent_(NULL), |
144 custom_window_shape_(NULL), | 145 window_shape_(NULL), |
146 custom_window_shape_(false), | |
145 urgency_hint_set_(false) { | 147 urgency_hint_set_(false) { |
146 } | 148 } |
147 | 149 |
148 DesktopWindowTreeHostX11::~DesktopWindowTreeHostX11() { | 150 DesktopWindowTreeHostX11::~DesktopWindowTreeHostX11() { |
149 window()->ClearProperty(kHostForRootWindow); | 151 window()->ClearProperty(kHostForRootWindow); |
150 aura::client::SetWindowMoveClient(window(), NULL); | 152 aura::client::SetWindowMoveClient(window(), NULL); |
151 desktop_native_widget_aura_->OnDesktopWindowTreeHostDestroyed(this); | 153 desktop_native_widget_aura_->OnDesktopWindowTreeHostDestroyed(this); |
152 if (custom_window_shape_) | 154 if (window_shape_) |
153 XDestroyRegion(custom_window_shape_); | 155 XDestroyRegion(window_shape_); |
154 DestroyDispatcher(); | 156 DestroyDispatcher(); |
155 } | 157 } |
156 | 158 |
157 // static | 159 // static |
158 aura::Window* DesktopWindowTreeHostX11::GetContentWindowForXID(XID xid) { | 160 aura::Window* DesktopWindowTreeHostX11::GetContentWindowForXID(XID xid) { |
159 aura::WindowTreeHost* host = | 161 aura::WindowTreeHost* host = |
160 aura::WindowTreeHost::GetForAcceleratedWidget(xid); | 162 aura::WindowTreeHost::GetForAcceleratedWidget(xid); |
161 return host ? host->window()->GetProperty(kViewsWindowForRootWindow) : NULL; | 163 return host ? host->window()->GetProperty(kViewsWindowForRootWindow) : NULL; |
162 } | 164 } |
163 | 165 |
(...skipping 11 matching lines...) Expand all Loading... | |
175 open_windows().end(), | 177 open_windows().end(), |
176 windows.begin(), | 178 windows.begin(), |
177 GetContentWindowForXID); | 179 GetContentWindowForXID); |
178 return windows; | 180 return windows; |
179 } | 181 } |
180 | 182 |
181 gfx::Rect DesktopWindowTreeHostX11::GetX11RootWindowBounds() const { | 183 gfx::Rect DesktopWindowTreeHostX11::GetX11RootWindowBounds() const { |
182 return bounds_; | 184 return bounds_; |
183 } | 185 } |
184 | 186 |
187 gfx::Rect DesktopWindowTreeHostX11::GetX11RootWindowOuterBounds() const { | |
188 gfx::Rect outer_bounds(bounds_); | |
189 outer_bounds.Inset(-native_window_frame_borders_); | |
190 return outer_bounds; | |
191 } | |
192 | |
193 ::Region DesktopWindowTreeHostX11::GetWindowShape() const { | |
194 return window_shape_; | |
195 } | |
196 | |
185 void DesktopWindowTreeHostX11::HandleNativeWidgetActivationChanged( | 197 void DesktopWindowTreeHostX11::HandleNativeWidgetActivationChanged( |
186 bool active) { | 198 bool active) { |
187 if (active) { | 199 if (active) { |
188 FlashFrame(false); | 200 FlashFrame(false); |
189 OnHostActivated(); | 201 OnHostActivated(); |
190 open_windows().remove(xwindow_); | 202 open_windows().remove(xwindow_); |
191 open_windows().insert(open_windows().begin(), xwindow_); | 203 open_windows().insert(open_windows().begin(), xwindow_); |
192 } | 204 } |
193 | 205 |
194 desktop_native_widget_aura_->HandleActivationChanged(active); | 206 desktop_native_widget_aura_->HandleActivationChanged(active); |
(...skipping 131 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
326 | 338 |
327 desktop_native_widget_aura_->OnHostClosed(); | 339 desktop_native_widget_aura_->OnHostClosed(); |
328 } | 340 } |
329 | 341 |
330 aura::WindowTreeHost* DesktopWindowTreeHostX11::AsWindowTreeHost() { | 342 aura::WindowTreeHost* DesktopWindowTreeHostX11::AsWindowTreeHost() { |
331 return this; | 343 return this; |
332 } | 344 } |
333 | 345 |
334 void DesktopWindowTreeHostX11::ShowWindowWithState( | 346 void DesktopWindowTreeHostX11::ShowWindowWithState( |
335 ui::WindowShowState show_state) { | 347 ui::WindowShowState show_state) { |
336 if (!window_mapped_) | 348 if (!window_mapped_) { |
337 MapWindow(show_state); | 349 MapWindow(show_state); |
pkotwicz
2014/05/09 21:11:08
I added a call to ResetWindowRegion() here. This o
| |
350 ResetWindowRegion(); | |
351 } | |
338 | 352 |
339 if (show_state == ui::SHOW_STATE_NORMAL || | 353 if (show_state == ui::SHOW_STATE_NORMAL || |
340 show_state == ui::SHOW_STATE_MAXIMIZED) { | 354 show_state == ui::SHOW_STATE_MAXIMIZED) { |
341 // Note: XFCE ignores a maximize hint given before mapping the window. | 355 // Note: XFCE ignores a maximize hint given before mapping the window. |
342 if (show_state == ui::SHOW_STATE_MAXIMIZED) | 356 if (show_state == ui::SHOW_STATE_MAXIMIZED) |
343 Maximize(); | 357 Maximize(); |
344 Activate(); | 358 Activate(); |
345 } | 359 } |
346 | 360 |
347 native_widget_delegate_->AsWidget()->SetInitialFocus(show_state); | 361 native_widget_delegate_->AsWidget()->SetInitialFocus(show_state); |
(...skipping 110 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
458 if (!XGetGeometry(xdisplay_, x_root_window_, &root, &x, &y, | 472 if (!XGetGeometry(xdisplay_, x_root_window_, &root, &x, &y, |
459 &width, &height, &border_width, &depth)) { | 473 &width, &height, &border_width, &depth)) { |
460 NOTIMPLEMENTED(); | 474 NOTIMPLEMENTED(); |
461 return gfx::Rect(0, 0, 10, 10); | 475 return gfx::Rect(0, 0, 10, 10); |
462 } | 476 } |
463 | 477 |
464 return gfx::Rect(x, y, width, height); | 478 return gfx::Rect(x, y, width, height); |
465 } | 479 } |
466 | 480 |
467 void DesktopWindowTreeHostX11::SetShape(gfx::NativeRegion native_region) { | 481 void DesktopWindowTreeHostX11::SetShape(gfx::NativeRegion native_region) { |
468 if (custom_window_shape_) | 482 if (window_shape_) |
469 XDestroyRegion(custom_window_shape_); | 483 XDestroyRegion(window_shape_); |
470 custom_window_shape_ = gfx::CreateRegionFromSkRegion(*native_region); | 484 custom_window_shape_ = true; |
485 window_shape_ = gfx::CreateRegionFromSkRegion(*native_region); | |
471 ResetWindowRegion(); | 486 ResetWindowRegion(); |
472 delete native_region; | 487 delete native_region; |
473 } | 488 } |
474 | 489 |
475 void DesktopWindowTreeHostX11::Activate() { | 490 void DesktopWindowTreeHostX11::Activate() { |
476 if (!window_mapped_) | 491 if (!window_mapped_) |
477 return; | 492 return; |
478 | 493 |
479 X11DesktopHandler::get()->ActivateWindow(xwindow_); | 494 X11DesktopHandler::get()->ActivateWindow(xwindow_); |
480 } | 495 } |
(...skipping 628 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
1109 CreateCompositor(GetAcceleratedWidget()); | 1124 CreateCompositor(GetAcceleratedWidget()); |
1110 } | 1125 } |
1111 | 1126 |
1112 bool DesktopWindowTreeHostX11::IsWindowManagerPresent() { | 1127 bool DesktopWindowTreeHostX11::IsWindowManagerPresent() { |
1113 // Per ICCCM 2.8, "Manager Selections", window managers should take ownership | 1128 // Per ICCCM 2.8, "Manager Selections", window managers should take ownership |
1114 // of WM_Sn selections (where n is a screen number). | 1129 // of WM_Sn selections (where n is a screen number). |
1115 return XGetSelectionOwner( | 1130 return XGetSelectionOwner( |
1116 xdisplay_, atom_cache_.GetAtom("WM_S0")) != None; | 1131 xdisplay_, atom_cache_.GetAtom("WM_S0")) != None; |
1117 } | 1132 } |
1118 | 1133 |
1134 void DesktopWindowTreeHostX11::OnWMStateUpdated() { | |
1135 std::vector< ::Atom> atom_list; | |
1136 if (!ui::GetAtomArrayProperty(xwindow_, "_NET_WM_STATE", &atom_list)) | |
1137 return; | |
1138 | |
1139 window_properties_.clear(); | |
1140 std::copy(atom_list.begin(), atom_list.end(), | |
1141 inserter(window_properties_, window_properties_.begin())); | |
1142 | |
1143 if (!restored_bounds_.IsEmpty() && !IsMaximized()) { | |
1144 // If we have restored bounds, but WM_STATE no longer claims to be | |
1145 // maximized, we should clear our restored bounds. | |
1146 restored_bounds_ = gfx::Rect(); | |
1147 } else if (IsMaximized() && restored_bounds_.IsEmpty()) { | |
1148 // The request that we become maximized originated from a different process. | |
1149 // |bounds_| already contains our maximized bounds. Do a best effort attempt | |
1150 // to get restored bounds by setting it to our previously set bounds (and if | |
1151 // we get this wrong, we aren't any worse off since we'd otherwise be | |
1152 // returning our maximized bounds). | |
1153 restored_bounds_ = previous_bounds_; | |
1154 } | |
1155 | |
1156 is_fullscreen_ = HasWMSpecProperty("_NET_WM_STATE_FULLSCREEN"); | |
1157 is_always_on_top_ = HasWMSpecProperty("_NET_WM_STATE_ABOVE"); | |
1158 | |
1159 // Now that we have different window properties, we may need to relayout the | |
1160 // window. (The windows code doesn't need this because their window change is | |
1161 // synchronous.) | |
1162 // | |
1163 // TODO(erg): While this does work, there's a quick flash showing the | |
1164 // tabstrip/toolbar/etc. when going into fullscreen mode before hiding those | |
1165 // parts of the UI because we receive the sizing event from the window | |
1166 // manager before we receive the event that changes the fullscreen state. | |
1167 // Unsure what to do about that. | |
1168 Widget* widget = native_widget_delegate_->AsWidget(); | |
1169 NonClientView* non_client_view = widget->non_client_view(); | |
1170 // non_client_view may be NULL, especially during creation. | |
1171 if (non_client_view) { | |
1172 non_client_view->client_view()->InvalidateLayout(); | |
1173 non_client_view->InvalidateLayout(); | |
1174 } | |
1175 widget->GetRootView()->Layout(); | |
1176 // Refresh the window's border, which may need to be updated if we have | |
1177 // changed the window's maximization state. | |
1178 ResetWindowRegion(); | |
1179 } | |
1180 | |
1181 void DesktopWindowTreeHostX11::OnFrameExtentsUpdated() { | |
1182 std::vector<int> insets; | |
1183 if (ui::GetIntArrayProperty(xwindow_, "_NET_FRAME_EXTENTS", &insets) && | |
1184 insets.size() == 4) { | |
1185 // |insets| are returned in the order: [left, right, top, bottom]. | |
1186 native_window_frame_borders_ = gfx::Insets( | |
1187 insets[2], | |
1188 insets[0], | |
1189 insets[3], | |
1190 insets[1]); | |
1191 } else { | |
1192 native_window_frame_borders_ = gfx::Insets(); | |
1193 } | |
1194 } | |
1195 | |
1119 void DesktopWindowTreeHostX11::SetWMSpecState(bool enabled, | 1196 void DesktopWindowTreeHostX11::SetWMSpecState(bool enabled, |
1120 ::Atom state1, | 1197 ::Atom state1, |
1121 ::Atom state2) { | 1198 ::Atom state2) { |
1122 XEvent xclient; | 1199 XEvent xclient; |
1123 memset(&xclient, 0, sizeof(xclient)); | 1200 memset(&xclient, 0, sizeof(xclient)); |
1124 xclient.type = ClientMessage; | 1201 xclient.type = ClientMessage; |
1125 xclient.xclient.window = xwindow_; | 1202 xclient.xclient.window = xwindow_; |
1126 xclient.xclient.message_type = atom_cache_.GetAtom("_NET_WM_STATE"); | 1203 xclient.xclient.message_type = atom_cache_.GetAtom("_NET_WM_STATE"); |
1127 xclient.xclient.format = 32; | 1204 xclient.xclient.format = 32; |
1128 xclient.xclient.data.l[0] = | 1205 xclient.xclient.data.l[0] = |
(...skipping 73 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
1202 g_current_capture->SendEventToProcessor(event); | 1279 g_current_capture->SendEventToProcessor(event); |
1203 } else { | 1280 } else { |
1204 SendEventToProcessor(event); | 1281 SendEventToProcessor(event); |
1205 } | 1282 } |
1206 } | 1283 } |
1207 | 1284 |
1208 void DesktopWindowTreeHostX11::ResetWindowRegion() { | 1285 void DesktopWindowTreeHostX11::ResetWindowRegion() { |
1209 // If a custom window shape was supplied then apply it. | 1286 // If a custom window shape was supplied then apply it. |
1210 if (custom_window_shape_) { | 1287 if (custom_window_shape_) { |
1211 XShapeCombineRegion( | 1288 XShapeCombineRegion( |
1212 xdisplay_, xwindow_, ShapeBounding, 0, 0, custom_window_shape_, false); | 1289 xdisplay_, xwindow_, ShapeBounding, 0, 0, window_shape_, false); |
1213 return; | 1290 return; |
1214 } | 1291 } |
1215 | 1292 |
1293 if (window_shape_) | |
1294 XDestroyRegion(window_shape_); | |
1295 window_shape_ = NULL; | |
1296 | |
1216 if (!IsMaximized()) { | 1297 if (!IsMaximized()) { |
1217 gfx::Path window_mask; | 1298 gfx::Path window_mask; |
1218 views::Widget* widget = native_widget_delegate_->AsWidget(); | 1299 views::Widget* widget = native_widget_delegate_->AsWidget(); |
1219 if (widget->non_client_view()) { | 1300 if (widget->non_client_view()) { |
1220 // Some frame views define a custom (non-rectangular) window mask. If | 1301 // Some frame views define a custom (non-rectangular) window mask. If |
1221 // so, use it to define the window shape. If not, fall through. | 1302 // so, use it to define the window shape. If not, fall through. |
1222 widget->non_client_view()->GetWindowMask(bounds_.size(), &window_mask); | 1303 widget->non_client_view()->GetWindowMask(bounds_.size(), &window_mask); |
1223 if (window_mask.countPoints() > 0) { | 1304 if (window_mask.countPoints() > 0) { |
1224 Region region = gfx::CreateRegionFromSkPath(window_mask); | 1305 window_shape_ = gfx::CreateRegionFromSkPath(window_mask); |
1225 XShapeCombineRegion(xdisplay_, xwindow_, ShapeBounding, | 1306 XShapeCombineRegion(xdisplay_, xwindow_, ShapeBounding, |
1226 0, 0, region, false); | 1307 0, 0, window_shape_, false); |
1227 XDestroyRegion(region); | |
1228 return; | 1308 return; |
1229 } | 1309 } |
1230 } | 1310 } |
1231 } | 1311 } |
1232 | 1312 |
1233 // If we didn't set the shape for any reason, reset the shaping information. | 1313 // If we didn't set the shape for any reason, reset the shaping information. |
1234 // How this is done depends on the border style, due to quirks and bugs in | 1314 // How this is done depends on the border style, due to quirks and bugs in |
1235 // various window managers. | 1315 // various window managers. |
1236 if (ShouldUseNativeFrame()) { | 1316 if (ShouldUseNativeFrame()) { |
1237 // If the window has system borders, the mask must be set to null (not a | 1317 // If the window has system borders, the mask must be set to null (not a |
(...skipping 362 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
1600 } else { | 1680 } else { |
1601 break; | 1681 break; |
1602 } | 1682 } |
1603 } | 1683 } |
1604 | 1684 |
1605 ui::MouseEvent mouseev(xev); | 1685 ui::MouseEvent mouseev(xev); |
1606 DispatchMouseEvent(&mouseev); | 1686 DispatchMouseEvent(&mouseev); |
1607 break; | 1687 break; |
1608 } | 1688 } |
1609 case PropertyNotify: { | 1689 case PropertyNotify: { |
1610 // Get our new window property state if the WM has told us its changed. | 1690 ::Atom changed_atom = xev->xproperty.atom; |
1611 ::Atom state = atom_cache_.GetAtom("_NET_WM_STATE"); | 1691 if (changed_atom == atom_cache_.GetAtom("_NET_WM_STATE")) |
1612 | 1692 OnWMStateUpdated(); |
1613 std::vector< ::Atom> atom_list; | 1693 else if (changed_atom == atom_cache_.GetAtom("_NET_FRAME_EXTENTS")) |
1614 if (xev->xproperty.atom == state && | 1694 OnFrameExtentsUpdated(); |
1615 ui::GetAtomArrayProperty(xwindow_, "_NET_WM_STATE", &atom_list)) { | |
1616 window_properties_.clear(); | |
1617 std::copy(atom_list.begin(), atom_list.end(), | |
1618 inserter(window_properties_, window_properties_.begin())); | |
1619 | |
1620 if (!restored_bounds_.IsEmpty() && !IsMaximized()) { | |
1621 // If we have restored bounds, but WM_STATE no longer claims to be | |
1622 // maximized, we should clear our restored bounds. | |
1623 restored_bounds_ = gfx::Rect(); | |
1624 } else if (IsMaximized() && restored_bounds_.IsEmpty()) { | |
1625 // The request that we become maximized originated from a different | |
1626 // process. |bounds_| already contains our maximized bounds. Do a | |
1627 // best effort attempt to get restored bounds by setting it to our | |
1628 // previously set bounds (and if we get this wrong, we aren't any | |
1629 // worse off since we'd otherwise be returning our maximized bounds). | |
1630 restored_bounds_ = previous_bounds_; | |
1631 } | |
1632 | |
1633 is_fullscreen_ = HasWMSpecProperty("_NET_WM_STATE_FULLSCREEN"); | |
1634 is_always_on_top_ = HasWMSpecProperty("_NET_WM_STATE_ABOVE"); | |
1635 | |
1636 // Now that we have different window properties, we may need to | |
1637 // relayout the window. (The windows code doesn't need this because | |
1638 // their window change is synchronous.) | |
1639 // | |
1640 // TODO(erg): While this does work, there's a quick flash showing the | |
1641 // tabstrip/toolbar/etc. when going into fullscreen mode before hiding | |
1642 // those parts of the UI because we receive the sizing event from the | |
1643 // window manager before we receive the event that changes the | |
1644 // fullscreen state. Unsure what to do about that. | |
1645 Widget* widget = native_widget_delegate_->AsWidget(); | |
1646 NonClientView* non_client_view = widget->non_client_view(); | |
1647 // non_client_view may be NULL, especially during creation. | |
1648 if (non_client_view) { | |
1649 non_client_view->client_view()->InvalidateLayout(); | |
1650 non_client_view->InvalidateLayout(); | |
1651 } | |
1652 widget->GetRootView()->Layout(); | |
1653 // Refresh the window's border, which may need to be updated if we have | |
1654 // changed the window's maximization state. | |
1655 ResetWindowRegion(); | |
1656 } | |
1657 break; | 1695 break; |
1658 } | 1696 } |
1659 case SelectionNotify: { | 1697 case SelectionNotify: { |
1660 drag_drop_client_->OnSelectionNotify(xev->xselection); | 1698 drag_drop_client_->OnSelectionNotify(xev->xselection); |
1661 break; | 1699 break; |
1662 } | 1700 } |
1663 } | 1701 } |
1664 return ui::POST_DISPATCH_STOP_PROPAGATION; | 1702 return ui::POST_DISPATCH_STOP_PROPAGATION; |
1665 } | 1703 } |
1666 | 1704 |
(...skipping 14 matching lines...) Expand all Loading... | |
1681 if (linux_ui) { | 1719 if (linux_ui) { |
1682 ui::NativeTheme* native_theme = linux_ui->GetNativeTheme(window); | 1720 ui::NativeTheme* native_theme = linux_ui->GetNativeTheme(window); |
1683 if (native_theme) | 1721 if (native_theme) |
1684 return native_theme; | 1722 return native_theme; |
1685 } | 1723 } |
1686 | 1724 |
1687 return ui::NativeTheme::instance(); | 1725 return ui::NativeTheme::instance(); |
1688 } | 1726 } |
1689 | 1727 |
1690 } // namespace views | 1728 } // namespace views |
OLD | NEW |