Chromium Code Reviews| 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/base/cursor/cursor_loader_x11.h" | 5 #include "ui/base/cursor/cursor_loader_x11.h" |
| 6 | 6 |
| 7 #include <float.h> | 7 #include <float.h> |
| 8 #include <X11/cursorfont.h> | 8 #include <X11/cursorfont.h> |
| 9 #include <X11/Xlib.h> | 9 #include <X11/Xlib.h> |
| 10 | 10 |
| 11 #include "base/logging.h" | 11 #include "base/logging.h" |
| 12 #include "build/build_config.h" | 12 #include "build/build_config.h" |
| 13 #include "skia/ext/image_operations.h" | 13 #include "skia/ext/image_operations.h" |
| 14 #include "ui/base/cursor/cursor.h" | 14 #include "ui/base/cursor/cursor.h" |
| 15 #include "ui/base/cursor/cursor_util.h" | 15 #include "ui/base/cursor/cursor_util.h" |
| 16 #include "ui/base/cursor/cursors_aura.h" | |
| 16 #include "ui/base/x/x11_util.h" | 17 #include "ui/base/x/x11_util.h" |
| 17 #include "ui/display/display.h" | 18 #include "ui/display/display.h" |
| 18 #include "ui/gfx/geometry/point_conversions.h" | 19 #include "ui/gfx/geometry/point_conversions.h" |
| 19 #include "ui/gfx/geometry/size_conversions.h" | 20 #include "ui/gfx/geometry/size_conversions.h" |
| 20 #include "ui/gfx/image/image.h" | 21 #include "ui/gfx/image/image.h" |
| 21 #include "ui/gfx/skbitmap_operations.h" | 22 #include "ui/gfx/skbitmap_operations.h" |
| 22 #include "ui/gfx/skia_util.h" | 23 #include "ui/gfx/skia_util.h" |
| 23 | 24 |
| 24 namespace { | 25 namespace { |
| 25 | 26 |
| 26 // Returns X font cursor shape from an Aura cursor. | 27 // Returns CSS cursor name from an Aura cursor ID. |
| 27 int CursorShapeFromNative(const gfx::NativeCursor& native_cursor) { | 28 const char* CursorCssNameFromId(int id) { |
| 28 switch (native_cursor.native_type()) { | 29 switch (id) { |
| 29 case ui::kCursorMiddlePanning: | 30 case ui::kCursorMiddlePanning: |
| 30 return XC_fleur; | |
| 31 case ui::kCursorEastPanning: | 31 case ui::kCursorEastPanning: |
| 32 return XC_sb_right_arrow; | |
| 33 case ui::kCursorNorthPanning: | 32 case ui::kCursorNorthPanning: |
| 34 return XC_sb_up_arrow; | |
| 35 case ui::kCursorNorthEastPanning: | 33 case ui::kCursorNorthEastPanning: |
| 36 return XC_top_right_corner; | |
| 37 case ui::kCursorNorthWestPanning: | 34 case ui::kCursorNorthWestPanning: |
| 38 return XC_top_left_corner; | |
| 39 case ui::kCursorSouthPanning: | 35 case ui::kCursorSouthPanning: |
| 40 return XC_sb_down_arrow; | |
| 41 case ui::kCursorSouthEastPanning: | 36 case ui::kCursorSouthEastPanning: |
| 42 return XC_bottom_right_corner; | |
| 43 case ui::kCursorSouthWestPanning: | 37 case ui::kCursorSouthWestPanning: |
| 44 return XC_bottom_left_corner; | |
| 45 case ui::kCursorWestPanning: | 38 case ui::kCursorWestPanning: |
| 46 return XC_sb_left_arrow; | 39 NOTREACHED() << "There's no middle-click panning on Linux or ChromeOS"; |
| 40 return "left_ptr"; | |
| 47 case ui::kCursorNone: | 41 case ui::kCursorNone: |
| 42 return "none"; | |
| 48 case ui::kCursorGrab: | 43 case ui::kCursorGrab: |
| 44 return "grab"; | |
| 49 case ui::kCursorGrabbing: | 45 case ui::kCursorGrabbing: |
| 50 // TODO(jamescook): Need cursors for these. crbug.com/111650 | 46 return "grabbing"; |
| 51 return XC_left_ptr; | |
| 52 | 47 |
| 53 #if defined(OS_CHROMEOS) | 48 #if defined(OS_CHROMEOS) |
| 54 case ui::kCursorNull: | 49 case ui::kCursorNull: |
| 55 case ui::kCursorPointer: | 50 case ui::kCursorPointer: |
| 56 case ui::kCursorNoDrop: | 51 case ui::kCursorNoDrop: |
| 57 case ui::kCursorNotAllowed: | 52 case ui::kCursorNotAllowed: |
| 58 case ui::kCursorCopy: | 53 case ui::kCursorCopy: |
| 59 case ui::kCursorMove: | 54 case ui::kCursorMove: |
| 60 case ui::kCursorEastResize: | 55 case ui::kCursorEastResize: |
| 61 case ui::kCursorNorthResize: | 56 case ui::kCursorNorthResize: |
| (...skipping 16 matching lines...) Expand all Loading... | |
| 78 case ui::kCursorNorthWestSouthEastResize: | 73 case ui::kCursorNorthWestSouthEastResize: |
| 79 case ui::kCursorProgress: | 74 case ui::kCursorProgress: |
| 80 case ui::kCursorColumnResize: | 75 case ui::kCursorColumnResize: |
| 81 case ui::kCursorRowResize: | 76 case ui::kCursorRowResize: |
| 82 case ui::kCursorVerticalText: | 77 case ui::kCursorVerticalText: |
| 83 case ui::kCursorZoomIn: | 78 case ui::kCursorZoomIn: |
| 84 case ui::kCursorZoomOut: | 79 case ui::kCursorZoomOut: |
| 85 case ui::kCursorHand: | 80 case ui::kCursorHand: |
| 86 // In some environments, the image assets are not set (e.g. in | 81 // In some environments, the image assets are not set (e.g. in |
| 87 // content-browsertests, content-shell etc.). | 82 // content-browsertests, content-shell etc.). |
| 88 return XC_left_ptr; | 83 return "left_ptr"; |
| 89 #else // defined(OS_CHROMEOS) | 84 #else // defined(OS_CHROMEOS) |
| 90 case ui::kCursorNull: | 85 case ui::kCursorNull: |
| 91 return XC_left_ptr; | 86 return "left_ptr"; |
| 92 case ui::kCursorPointer: | 87 case ui::kCursorPointer: |
| 93 return XC_left_ptr; | 88 return "left_ptr"; |
| 94 case ui::kCursorMove: | 89 case ui::kCursorMove: |
| 95 return XC_fleur; | 90 return "move"; |
| 96 case ui::kCursorCross: | 91 case ui::kCursorCross: |
| 97 return XC_crosshair; | 92 return "crosshair"; |
| 98 case ui::kCursorHand: | 93 case ui::kCursorHand: |
| 99 return XC_hand2; | 94 return "pointer"; |
| 100 case ui::kCursorIBeam: | 95 case ui::kCursorIBeam: |
| 101 return XC_xterm; | 96 return "text"; |
| 102 case ui::kCursorProgress: | 97 case ui::kCursorProgress: |
| 98 return "progress"; | |
| 103 case ui::kCursorWait: | 99 case ui::kCursorWait: |
| 104 return XC_watch; | 100 return "wait"; |
| 105 case ui::kCursorHelp: | 101 case ui::kCursorHelp: |
| 106 return XC_question_arrow; | 102 return "help"; |
| 107 case ui::kCursorEastResize: | 103 case ui::kCursorEastResize: |
| 108 return XC_right_side; | 104 return "e-resize"; |
| 109 case ui::kCursorNorthResize: | 105 case ui::kCursorNorthResize: |
| 110 return XC_top_side; | 106 return "n-resize"; |
| 111 case ui::kCursorNorthEastResize: | 107 case ui::kCursorNorthEastResize: |
| 112 return XC_top_right_corner; | 108 return "ne-resize"; |
| 113 case ui::kCursorNorthWestResize: | 109 case ui::kCursorNorthWestResize: |
| 114 return XC_top_left_corner; | 110 return "nw-resize"; |
| 115 case ui::kCursorSouthResize: | 111 case ui::kCursorSouthResize: |
| 116 return XC_bottom_side; | 112 return "s-resize"; |
| 117 case ui::kCursorSouthEastResize: | 113 case ui::kCursorSouthEastResize: |
| 118 return XC_bottom_right_corner; | 114 return "se-resize"; |
| 119 case ui::kCursorSouthWestResize: | 115 case ui::kCursorSouthWestResize: |
| 120 return XC_bottom_left_corner; | 116 return "sw-resize"; |
| 121 case ui::kCursorWestResize: | 117 case ui::kCursorWestResize: |
| 122 return XC_left_side; | 118 return "w-resize"; |
| 123 case ui::kCursorNorthSouthResize: | 119 case ui::kCursorNorthSouthResize: |
| 124 return XC_sb_v_double_arrow; | 120 return "ns-resize"; |
| 125 case ui::kCursorEastWestResize: | 121 case ui::kCursorEastWestResize: |
| 126 return XC_sb_h_double_arrow; | 122 return "ew-resize"; |
| 127 case ui::kCursorColumnResize: | 123 case ui::kCursorColumnResize: |
| 128 return XC_sb_h_double_arrow; | 124 return "col-resize"; |
| 129 case ui::kCursorRowResize: | 125 case ui::kCursorRowResize: |
| 130 return XC_sb_v_double_arrow; | 126 return "row-resize"; |
| 127 case ui::kCursorNorthEastSouthWestResize: | |
| 128 return "nesw-resize"; | |
| 129 case ui::kCursorNorthWestSouthEastResize: | |
| 130 return "nwse-resize"; | |
| 131 case ui::kCursorVerticalText: | |
| 132 return "vertical-text"; | |
| 133 case ui::kCursorZoomIn: | |
| 134 return "zoom-in"; | |
| 135 case ui::kCursorZoomOut: | |
| 136 return "zoom-out"; | |
| 137 case ui::kCursorCell: | |
| 138 return "cell"; | |
| 139 case ui::kCursorContextMenu: | |
| 140 return "context-menu"; | |
| 141 case ui::kCursorAlias: | |
| 142 return "alias"; | |
| 143 case ui::kCursorNoDrop: | |
| 144 return "no-drop"; | |
| 145 case ui::kCursorCopy: | |
| 146 return "copy"; | |
| 147 case ui::kCursorNotAllowed: | |
| 148 return "not-allowed"; | |
| 149 case ui::kCursorDndNone: | |
| 150 return "dnd-none"; | |
| 151 case ui::kCursorDndMove: | |
| 152 return "dnd-move"; | |
| 153 case ui::kCursorDndCopy: | |
| 154 return "dnd-copy"; | |
| 155 case ui::kCursorDndLink: | |
| 156 return "dnd-link"; | |
| 131 #endif // defined(OS_CHROMEOS) | 157 #endif // defined(OS_CHROMEOS) |
| 132 case ui::kCursorCustom: | 158 case ui::kCursorCustom: |
| 133 NOTREACHED(); | 159 NOTREACHED(); |
| 134 return XC_left_ptr; | 160 return "left_ptr"; |
| 135 } | 161 } |
| 136 NOTREACHED() << "Case not handled for " << native_cursor.native_type(); | 162 NOTREACHED() << "Case not handled for " << id; |
| 137 return XC_left_ptr; | 163 return "left_ptr"; |
| 138 } | 164 } |
| 139 | 165 |
| 166 static const struct { | |
| 167 const char* css_name; | |
| 168 const char* traditional_name; | |
| 169 int shape; | |
| 170 } kCursorFallbacks[] = { | |
| 171 { "pointer", "hand", XC_hand1 }, | |
| 172 { "progress", "left_ptr_watch", XC_watch }, | |
| 173 { "wait", nullptr, XC_watch }, | |
| 174 { "cell", nullptr, XC_plus }, | |
| 175 { "crosshair", nullptr, XC_cross }, | |
| 176 { "text", nullptr, XC_xterm }, | |
| 177 { "not-allowed", "crossed_circle", None }, | |
| 178 { "grabbing", nullptr, XC_hand2 }, | |
| 179 { "col-resize", nullptr, XC_sb_h_double_arrow }, | |
| 180 { "row-resize", nullptr, XC_sb_v_double_arrow}, | |
| 181 { "n-resize", nullptr, XC_top_side}, | |
| 182 { "e-resize", nullptr, XC_right_side}, | |
| 183 { "s-resize", nullptr, XC_bottom_side}, | |
| 184 { "w-resize", nullptr, XC_left_side}, | |
| 185 { "ne-resize", nullptr, XC_top_right_corner}, | |
| 186 { "nw-resize", nullptr, XC_top_left_corner}, | |
| 187 { "se-resize", nullptr, XC_bottom_right_corner}, | |
| 188 { "sw-resize", nullptr, XC_bottom_left_corner}, | |
| 189 { "ew-resize", nullptr, XC_sb_h_double_arrow}, | |
| 190 { "ns-resize", nullptr, XC_sb_v_double_arrow}, | |
| 191 { "nesw-resize", "fd_double_arrow", None}, | |
| 192 { "nwse-resize", "bd_double_arrow", None}, | |
| 193 }; | |
| 194 | |
| 140 } // namespace | 195 } // namespace |
| 141 | 196 |
| 142 namespace ui { | 197 namespace ui { |
| 143 | 198 |
| 144 CursorLoader* CursorLoader::Create() { | 199 CursorLoader* CursorLoader::Create() { |
| 145 return new CursorLoaderX11; | 200 return new CursorLoaderX11; |
| 146 } | 201 } |
| 147 | 202 |
| 203 CursorLoaderX11::ImageCursor::ImageCursor(XcursorImage* x_image, | |
| 204 float scale, | |
| 205 display::Display::Rotation rotation) | |
| 206 : scale(scale), rotation(rotation) { | |
| 207 cursor = CreateReffedCustomXCursor(x_image); | |
| 208 } | |
| 209 | |
| 210 CursorLoaderX11::ImageCursor::~ImageCursor() { | |
| 211 UnrefCustomXCursor(cursor); | |
| 212 } | |
| 213 | |
| 148 CursorLoaderX11::CursorLoaderX11() | 214 CursorLoaderX11::CursorLoaderX11() |
| 149 : invisible_cursor_(CreateInvisibleCursor(), gfx::GetXDisplay()) { | 215 : display_(gfx::GetXDisplay()), |
| 150 } | 216 invisible_cursor_(CreateInvisibleCursor(), gfx::GetXDisplay()) {} |
| 151 | 217 |
| 152 CursorLoaderX11::~CursorLoaderX11() { | 218 CursorLoaderX11::~CursorLoaderX11() { |
| 153 UnloadAll(); | 219 UnloadAll(); |
| 154 } | 220 } |
| 155 | 221 |
| 156 void CursorLoaderX11::LoadImageCursor(int id, | 222 void CursorLoaderX11::LoadImageCursor(int id, |
| 157 int resource_id, | 223 int resource_id, |
| 158 const gfx::Point& hot) { | 224 const gfx::Point& hot) { |
| 159 SkBitmap bitmap; | 225 SkBitmap bitmap; |
| 160 gfx::Point hotspot = hot; | 226 gfx::Point hotspot = hot; |
| 161 | 227 |
| 162 GetImageCursorBitmap(resource_id, scale(), rotation(), &hotspot, &bitmap); | 228 GetImageCursorBitmap(resource_id, scale(), rotation(), &hotspot, &bitmap); |
| 163 XcursorImage* x_image = SkBitmapToXcursorImage(&bitmap, hotspot); | 229 XcursorImage* x_image = SkBitmapToXcursorImage(&bitmap, hotspot); |
| 164 cursors_[id] = CreateReffedCustomXCursor(x_image); | 230 image_cursors_[id].reset(new ImageCursor(x_image, scale(), rotation())); |
| 165 } | 231 } |
| 166 | 232 |
| 167 void CursorLoaderX11::LoadAnimatedCursor(int id, | 233 void CursorLoaderX11::LoadAnimatedCursor(int id, |
| 168 int resource_id, | 234 int resource_id, |
| 169 const gfx::Point& hot, | 235 const gfx::Point& hot, |
| 170 int frame_delay_ms) { | 236 int frame_delay_ms) { |
| 171 std::vector<SkBitmap> bitmaps; | 237 std::vector<SkBitmap> bitmaps; |
| 172 gfx::Point hotspot = hot; | 238 gfx::Point hotspot = hot; |
| 173 | 239 |
| 174 GetAnimatedCursorBitmaps( | 240 GetAnimatedCursorBitmaps(resource_id, scale(), rotation(), &hotspot, |
| 175 resource_id, scale(), rotation(), &hotspot, &bitmaps); | 241 &bitmaps); |
| 176 | 242 |
| 177 XcursorImages* x_images = XcursorImagesCreate(bitmaps.size()); | 243 XcursorImages* x_images = XcursorImagesCreate(bitmaps.size()); |
| 178 x_images->nimage = bitmaps.size(); | 244 x_images->nimage = bitmaps.size(); |
| 179 | 245 |
| 180 for (unsigned int frame = 0; frame < bitmaps.size(); ++frame) { | 246 for (unsigned int frame = 0; frame < bitmaps.size(); ++frame) { |
| 181 XcursorImage* x_image = SkBitmapToXcursorImage(&bitmaps[frame], hotspot); | 247 XcursorImage* x_image = SkBitmapToXcursorImage(&bitmaps[frame], hotspot); |
| 182 x_image->delay = frame_delay_ms; | 248 x_image->delay = frame_delay_ms; |
| 183 x_images->images[frame] = x_image; | 249 x_images->images[frame] = x_image; |
| 184 } | 250 } |
| 185 | 251 |
| 186 animated_cursors_[id] = std::make_pair( | 252 animated_cursors_[id] = std::make_pair( |
| 187 XcursorImagesLoadCursor(gfx::GetXDisplay(), x_images), x_images); | 253 XcursorImagesLoadCursor(gfx::GetXDisplay(), x_images), x_images); |
| 188 } | 254 } |
| 189 | 255 |
| 190 void CursorLoaderX11::UnloadAll() { | 256 void CursorLoaderX11::UnloadAll() { |
| 191 for (ImageCursorMap::const_iterator it = cursors_.begin(); | 257 image_cursors_.clear(); |
| 192 it != cursors_.end(); ++it) | |
| 193 UnrefCustomXCursor(it->second); | |
| 194 | 258 |
| 195 // Free animated cursors and images. | 259 // Free animated cursors and images. |
| 196 for (AnimatedCursorMap::iterator it = animated_cursors_.begin(); | 260 for (const auto& cursor : animated_cursors_) { |
| 197 it != animated_cursors_.end(); ++it) { | 261 XcursorImagesDestroy( |
| 198 XcursorImagesDestroy(it->second.second); // also frees individual frames. | 262 cursor.second.second); // also frees individual frames. |
| 199 XFreeCursor(gfx::GetXDisplay(), it->second.first); | 263 XFreeCursor(gfx::GetXDisplay(), cursor.second.first); |
| 200 } | 264 } |
| 201 } | 265 } |
| 202 | 266 |
| 203 void CursorLoaderX11::SetPlatformCursor(gfx::NativeCursor* cursor) { | 267 void CursorLoaderX11::SetPlatformCursor(gfx::NativeCursor* cursor) { |
| 204 DCHECK(cursor); | 268 DCHECK(cursor); |
| 205 | 269 |
| 206 ::Cursor xcursor; | 270 if (*cursor == kCursorNone) { |
| 207 if (IsImageCursor(*cursor)) | 271 cursor->SetPlatformCursor(invisible_cursor_.get()); |
| 208 xcursor = ImageCursorFromNative(*cursor); | 272 return; |
| 209 else if (*cursor == kCursorNone) | |
| 210 xcursor = invisible_cursor_.get(); | |
| 211 else if (*cursor == kCursorCustom) | |
| 212 xcursor = cursor->platform(); | |
| 213 else if (scale() == 1.0f && rotation() == display::Display::ROTATE_0) { | |
| 214 xcursor = GetXCursor(CursorShapeFromNative(*cursor)); | |
| 215 } else { | |
| 216 xcursor = ImageCursorFromNative(kCursorPointer); | |
| 217 } | 273 } |
| 218 | 274 |
| 219 cursor->SetPlatformCursor(xcursor); | 275 if (*cursor == kCursorCustom) |
| 276 return; | |
| 277 | |
| 278 cursor->SetPlatformCursor(CursorFromId(cursor->native_type())); | |
| 220 } | 279 } |
| 221 | 280 |
| 222 const XcursorImage* CursorLoaderX11::GetXcursorImageForTest(int id) { | 281 const XcursorImage* CursorLoaderX11::GetXcursorImageForTest(int id) { |
| 223 return test::GetCachedXcursorImage(cursors_[id]); | 282 return test::GetCachedXcursorImage(image_cursors_[id]->cursor); |
| 224 } | 283 } |
| 225 | 284 |
| 226 bool CursorLoaderX11::IsImageCursor(gfx::NativeCursor native_cursor) { | 285 bool CursorLoaderX11::IsImageCursor(gfx::NativeCursor native_cursor) { |
| 227 int type = native_cursor.native_type(); | 286 int type = native_cursor.native_type(); |
| 228 return cursors_.count(type) || animated_cursors_.count(type); | 287 return image_cursors_.count(type) || animated_cursors_.count(type); |
| 229 } | 288 } |
| 230 | 289 |
| 231 ::Cursor CursorLoaderX11::ImageCursorFromNative( | 290 ::Cursor CursorLoaderX11::CursorFromId(int id) { |
| 232 gfx::NativeCursor native_cursor) { | 291 const char* css_name = CursorCssNameFromId(id); |
| 233 int type = native_cursor.native_type(); | |
| 234 if (animated_cursors_.count(type)) | |
| 235 return animated_cursors_[type].first; | |
| 236 | 292 |
| 237 ImageCursorMap::iterator find = cursors_.find(type); | 293 auto font_it = font_cursors_.find(id); |
| 238 if (find != cursors_.end()) | 294 if (font_it != font_cursors_.end()) |
| 239 return cursors_[type]; | 295 return font_it->second; |
| 240 return GetXCursor(CursorShapeFromNative(native_cursor)); | 296 auto image_it = image_cursors_.find(id); |
| 297 if (image_it != image_cursors_.end()) { | |
| 298 if (image_it->second->scale == scale() && | |
| 299 image_it->second->rotation == rotation()) { | |
| 300 return image_it->second->cursor; | |
| 301 } else { | |
| 302 image_cursors_.erase(image_it); | |
| 303 } | |
| 304 } | |
| 305 | |
| 306 // First try to load the cursor directly. | |
| 307 ::Cursor cursor = XcursorLibraryLoadCursor(display_, css_name); | |
|
sadrul
2016/11/17 16:17:03
When you say 'directly', do you mean like there ca
Tom (Use chromium acct)
2016/11/17 19:17:48
That's right
| |
| 308 if (cursor == None) { | |
| 309 // Try a similar cursor supplied by the native cursor theme. | |
| 310 for (const auto& mapping : kCursorFallbacks) { | |
| 311 if (strcmp(mapping.css_name, css_name) == 0) { | |
| 312 if (mapping.traditional_name) | |
| 313 cursor = XcursorLibraryLoadCursor(display_, mapping.traditional_name); | |
| 314 if (cursor == None && mapping.shape) | |
| 315 cursor = XCreateFontCursor(display_, mapping.shape); | |
| 316 } | |
| 317 } | |
| 318 } | |
| 319 if (cursor != None) { | |
| 320 font_cursors_[id] = cursor; | |
| 321 return cursor; | |
| 322 } | |
| 323 | |
| 324 // If the theme is missing the desired cursor, use a chromium-supplied | |
| 325 // fallback icon. | |
| 326 int resource_id; | |
| 327 gfx::Point point; | |
| 328 if (ui::GetCursorDataFor(ui::CURSOR_SET_NORMAL, id, scale(), &resource_id, | |
| 329 &point)) { | |
| 330 LoadImageCursor(id, resource_id, point); | |
| 331 return image_cursors_[id]->cursor; | |
| 332 } | |
| 333 | |
| 334 // As a last resort, return a left pointer. | |
| 335 cursor = XCreateFontCursor(display_, XC_left_ptr); | |
| 336 DCHECK(cursor); | |
| 337 font_cursors_[id] = cursor; | |
| 338 return cursor; | |
| 241 } | 339 } |
| 242 | 340 |
| 243 } // namespace ui | 341 } // namespace ui |
| OLD | NEW |