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 "ui/base/text/text_elider.h" | 5 #include "ui/base/text/text_elider.h" |
6 | 6 |
7 #include <string> | 7 #include <string> |
8 #include <vector> | 8 #include <vector> |
9 | 9 |
10 #include "base/file_path.h" | 10 #include "base/file_path.h" |
(...skipping 76 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
87 int available_pixel_width) { | 87 int available_pixel_width) { |
88 size_t url_path_number_of_elements = url_path_elements.size(); | 88 size_t url_path_number_of_elements = url_path_elements.size(); |
89 | 89 |
90 const string16 kEllipsisAndSlash = UTF8ToUTF16(kEllipsis) + kForwardSlash; | 90 const string16 kEllipsisAndSlash = UTF8ToUTF16(kEllipsis) + kForwardSlash; |
91 | 91 |
92 for (size_t i = url_path_number_of_elements - 1; i > 0; --i) { | 92 for (size_t i = url_path_number_of_elements - 1; i > 0; --i) { |
93 string16 elided_path = BuildPathFromComponents(url_path_prefix, | 93 string16 elided_path = BuildPathFromComponents(url_path_prefix, |
94 url_path_elements, url_filename, i); | 94 url_path_elements, url_filename, i); |
95 if (available_pixel_width >= font.GetStringWidth(elided_path)) | 95 if (available_pixel_width >= font.GetStringWidth(elided_path)) |
96 return ElideText(elided_path + url_query, | 96 return ElideText(elided_path + url_query, |
97 font, available_pixel_width, false); | 97 font, available_pixel_width, ui::ELIDE_AT_END); |
98 } | 98 } |
99 | 99 |
100 return string16(); | 100 return string16(); |
101 } | 101 } |
102 | 102 |
103 } // namespace | 103 } // namespace |
104 | 104 |
105 // This function takes a GURL object and elides it. It returns a string | 105 // This function takes a GURL object and elides it. It returns a string |
106 // which composed of parts from subdomain, domain, path, filename and query. | 106 // which composed of parts from subdomain, domain, path, filename and query. |
107 // A "..." is added automatically at the end if the elided string is bigger | 107 // A "..." is added automatically at the end if the elided string is bigger |
(...skipping 10 matching lines...) Expand all Loading... |
118 const std::string& languages) { | 118 const std::string& languages) { |
119 // Get a formatted string and corresponding parsing of the url. | 119 // Get a formatted string and corresponding parsing of the url. |
120 url_parse::Parsed parsed; | 120 url_parse::Parsed parsed; |
121 string16 url_string = net::FormatUrl(url, languages, net::kFormatUrlOmitAll, | 121 string16 url_string = net::FormatUrl(url, languages, net::kFormatUrlOmitAll, |
122 net::UnescapeRule::SPACES, &parsed, NULL, NULL); | 122 net::UnescapeRule::SPACES, &parsed, NULL, NULL); |
123 if (available_pixel_width <= 0) | 123 if (available_pixel_width <= 0) |
124 return url_string; | 124 return url_string; |
125 | 125 |
126 // If non-standard or not file type, return plain eliding. | 126 // If non-standard or not file type, return plain eliding. |
127 if (!(url.SchemeIsFile() || url.IsStandard())) | 127 if (!(url.SchemeIsFile() || url.IsStandard())) |
128 return ElideText(url_string, font, available_pixel_width, false); | 128 return ElideText(url_string, font, available_pixel_width, ui::ELIDE_AT_END); |
129 | 129 |
130 // Now start eliding url_string to fit within available pixel width. | 130 // Now start eliding url_string to fit within available pixel width. |
131 // Fist pass - check to see whether entire url_string fits. | 131 // Fist pass - check to see whether entire url_string fits. |
132 int pixel_width_url_string = font.GetStringWidth(url_string); | 132 int pixel_width_url_string = font.GetStringWidth(url_string); |
133 if (available_pixel_width >= pixel_width_url_string) | 133 if (available_pixel_width >= pixel_width_url_string) |
134 return url_string; | 134 return url_string; |
135 | 135 |
136 // Get the path substring, including query and reference. | 136 // Get the path substring, including query and reference. |
137 size_t path_start_index = parsed.path.begin; | 137 size_t path_start_index = parsed.path.begin; |
138 size_t path_len = parsed.path.len; | 138 size_t path_len = parsed.path.len; |
139 string16 url_path_query_etc = url_string.substr(path_start_index); | 139 string16 url_path_query_etc = url_string.substr(path_start_index); |
140 string16 url_path = url_string.substr(path_start_index, path_len); | 140 string16 url_path = url_string.substr(path_start_index, path_len); |
141 | 141 |
142 // Return general elided text if url minus the query fits. | 142 // Return general elided text if url minus the query fits. |
143 string16 url_minus_query = url_string.substr(0, path_start_index + path_len); | 143 string16 url_minus_query = url_string.substr(0, path_start_index + path_len); |
144 if (available_pixel_width >= font.GetStringWidth(url_minus_query)) | 144 if (available_pixel_width >= font.GetStringWidth(url_minus_query)) |
145 return ElideText(url_string, font, available_pixel_width, false); | 145 return ElideText(url_string, font, available_pixel_width, ui::ELIDE_AT_END); |
146 | 146 |
147 // Get Host. | 147 // Get Host. |
148 string16 url_host = UTF8ToUTF16(url.host()); | 148 string16 url_host = UTF8ToUTF16(url.host()); |
149 | 149 |
150 // Get domain and registry information from the URL. | 150 // Get domain and registry information from the URL. |
151 string16 url_domain = UTF8ToUTF16( | 151 string16 url_domain = UTF8ToUTF16( |
152 net::RegistryControlledDomainService::GetDomainAndRegistry(url)); | 152 net::RegistryControlledDomainService::GetDomainAndRegistry(url)); |
153 if (url_domain.empty()) | 153 if (url_domain.empty()) |
154 url_domain = url_host; | 154 url_domain = url_host; |
155 | 155 |
(...skipping 50 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
206 // Query element. | 206 // Query element. |
207 string16 url_query; | 207 string16 url_query; |
208 const int kPixelWidthDotsTrailer = | 208 const int kPixelWidthDotsTrailer = |
209 font.GetStringWidth(UTF8ToUTF16(kEllipsis)); | 209 font.GetStringWidth(UTF8ToUTF16(kEllipsis)); |
210 if (parsed.query.is_nonempty()) { | 210 if (parsed.query.is_nonempty()) { |
211 url_query = UTF8ToUTF16("?") + url_string.substr(parsed.query.begin); | 211 url_query = UTF8ToUTF16("?") + url_string.substr(parsed.query.begin); |
212 if (available_pixel_width >= (pixel_width_url_subdomain + | 212 if (available_pixel_width >= (pixel_width_url_subdomain + |
213 pixel_width_url_domain + pixel_width_url_path - | 213 pixel_width_url_domain + pixel_width_url_path - |
214 font.GetStringWidth(url_query))) { | 214 font.GetStringWidth(url_query))) { |
215 return ElideText(url_subdomain + url_domain + url_path_query_etc, | 215 return ElideText(url_subdomain + url_domain + url_path_query_etc, |
216 font, available_pixel_width, false); | 216 font, available_pixel_width, ui::ELIDE_AT_END); |
217 } | 217 } |
218 } | 218 } |
219 | 219 |
220 // Parse url_path using '/'. | 220 // Parse url_path using '/'. |
221 std::vector<string16> url_path_elements; | 221 std::vector<string16> url_path_elements; |
222 base::SplitString(url_path, kForwardSlash, &url_path_elements); | 222 base::SplitString(url_path, kForwardSlash, &url_path_elements); |
223 | 223 |
224 // Get filename - note that for a path ending with / | 224 // Get filename - note that for a path ending with / |
225 // such as www.google.com/intl/ads/, the file name is ads/. | 225 // such as www.google.com/intl/ads/, the file name is ads/. |
226 size_t url_path_number_of_elements = url_path_elements.size(); | 226 size_t url_path_number_of_elements = url_path_elements.size(); |
227 DCHECK(url_path_number_of_elements != 0); | 227 DCHECK(url_path_number_of_elements != 0); |
228 string16 url_filename; | 228 string16 url_filename; |
229 if ((url_path_elements.at(url_path_number_of_elements - 1)).length() > 0) { | 229 if ((url_path_elements.at(url_path_number_of_elements - 1)).length() > 0) { |
230 url_filename = *(url_path_elements.end() - 1); | 230 url_filename = *(url_path_elements.end() - 1); |
231 } else if (url_path_number_of_elements > 1) { // Path ends with a '/'. | 231 } else if (url_path_number_of_elements > 1) { // Path ends with a '/'. |
232 url_filename = url_path_elements.at(url_path_number_of_elements - 2) + | 232 url_filename = url_path_elements.at(url_path_number_of_elements - 2) + |
233 kForwardSlash; | 233 kForwardSlash; |
234 url_path_number_of_elements--; | 234 url_path_number_of_elements--; |
235 } | 235 } |
236 DCHECK(url_path_number_of_elements != 0); | 236 DCHECK(url_path_number_of_elements != 0); |
237 | 237 |
238 const size_t kMaxNumberOfUrlPathElementsAllowed = 1024; | 238 const size_t kMaxNumberOfUrlPathElementsAllowed = 1024; |
239 if (url_path_number_of_elements <= 1 || | 239 if (url_path_number_of_elements <= 1 || |
240 url_path_number_of_elements > kMaxNumberOfUrlPathElementsAllowed) { | 240 url_path_number_of_elements > kMaxNumberOfUrlPathElementsAllowed) { |
241 // No path to elide, or too long of a path (could overflow in loop below) | 241 // No path to elide, or too long of a path (could overflow in loop below) |
242 // Just elide this as a text string. | 242 // Just elide this as a text string. |
243 return ElideText(url_subdomain + url_domain + url_path_query_etc, font, | 243 return ElideText(url_subdomain + url_domain + url_path_query_etc, font, |
244 available_pixel_width, false); | 244 available_pixel_width, ui::ELIDE_AT_END); |
245 } | 245 } |
246 | 246 |
247 // Start eliding the path and replacing elements by ".../". | 247 // Start eliding the path and replacing elements by ".../". |
248 const string16 kEllipsisAndSlash = UTF8ToUTF16(kEllipsis) + kForwardSlash; | 248 const string16 kEllipsisAndSlash = UTF8ToUTF16(kEllipsis) + kForwardSlash; |
249 int pixel_width_ellipsis_slash = font.GetStringWidth(kEllipsisAndSlash); | 249 int pixel_width_ellipsis_slash = font.GetStringWidth(kEllipsisAndSlash); |
250 | 250 |
251 // Check with both subdomain and domain. | 251 // Check with both subdomain and domain. |
252 string16 elided_path = ElideComponentizedPath(url_subdomain + url_domain, | 252 string16 elided_path = ElideComponentizedPath(url_subdomain + url_domain, |
253 url_path_elements, url_filename, url_query, font, available_pixel_width); | 253 url_path_elements, url_filename, url_query, font, available_pixel_width); |
254 if (!elided_path.empty()) | 254 if (!elided_path.empty()) |
(...skipping 26 matching lines...) Expand all Loading... |
281 // A hack to prevent trailing ".../...". | 281 // A hack to prevent trailing ".../...". |
282 if ((available_pixel_width - url_elided_domain_width) > | 282 if ((available_pixel_width - url_elided_domain_width) > |
283 pixel_width_ellipsis_slash + kPixelWidthDotsTrailer + | 283 pixel_width_ellipsis_slash + kPixelWidthDotsTrailer + |
284 font.GetStringWidth(ASCIIToUTF16("UV"))) { | 284 font.GetStringWidth(ASCIIToUTF16("UV"))) { |
285 final_elided_url_string += BuildPathFromComponents(string16(), | 285 final_elided_url_string += BuildPathFromComponents(string16(), |
286 url_path_elements, url_filename, 1); | 286 url_path_elements, url_filename, 1); |
287 } else { | 287 } else { |
288 final_elided_url_string += url_path; | 288 final_elided_url_string += url_path; |
289 } | 289 } |
290 | 290 |
291 return ElideText(final_elided_url_string, font, available_pixel_width, false); | 291 return ElideText(final_elided_url_string, font, available_pixel_width, |
| 292 ui::ELIDE_AT_END); |
292 } | 293 } |
293 | 294 |
294 string16 ElideFilename(const FilePath& filename, | 295 string16 ElideFilename(const FilePath& filename, |
295 const gfx::Font& font, | 296 const gfx::Font& font, |
296 int available_pixel_width) { | 297 int available_pixel_width) { |
297 #if defined(OS_WIN) | 298 #if defined(OS_WIN) |
298 string16 filename_utf16 = filename.value(); | 299 string16 filename_utf16 = filename.value(); |
299 string16 extension = filename.Extension(); | 300 string16 extension = filename.Extension(); |
300 string16 rootname = filename.BaseName().RemoveExtension().value(); | 301 string16 rootname = filename.BaseName().RemoveExtension().value(); |
301 #elif defined(OS_POSIX) | 302 #elif defined(OS_POSIX) |
302 string16 filename_utf16 = WideToUTF16(base::SysNativeMBToWide( | 303 string16 filename_utf16 = WideToUTF16(base::SysNativeMBToWide( |
303 filename.value())); | 304 filename.value())); |
304 string16 extension = WideToUTF16(base::SysNativeMBToWide( | 305 string16 extension = WideToUTF16(base::SysNativeMBToWide( |
305 filename.Extension())); | 306 filename.Extension())); |
306 string16 rootname = WideToUTF16(base::SysNativeMBToWide( | 307 string16 rootname = WideToUTF16(base::SysNativeMBToWide( |
307 filename.BaseName().RemoveExtension().value())); | 308 filename.BaseName().RemoveExtension().value())); |
308 #endif | 309 #endif |
309 | 310 |
310 int full_width = font.GetStringWidth(filename_utf16); | 311 int full_width = font.GetStringWidth(filename_utf16); |
311 if (full_width <= available_pixel_width) | 312 if (full_width <= available_pixel_width) |
312 return base::i18n::GetDisplayStringInLTRDirectionality(filename_utf16); | 313 return base::i18n::GetDisplayStringInLTRDirectionality(filename_utf16); |
313 | 314 |
314 if (rootname.empty() || extension.empty()) { | 315 if (rootname.empty() || extension.empty()) { |
315 string16 elided_name = ElideText(filename_utf16, font, | 316 string16 elided_name = ElideText(filename_utf16, font, |
316 available_pixel_width, false); | 317 available_pixel_width, ui::ELIDE_AT_END); |
317 return base::i18n::GetDisplayStringInLTRDirectionality(elided_name); | 318 return base::i18n::GetDisplayStringInLTRDirectionality(elided_name); |
318 } | 319 } |
319 | 320 |
320 int ext_width = font.GetStringWidth(extension); | 321 int ext_width = font.GetStringWidth(extension); |
321 int root_width = font.GetStringWidth(rootname); | 322 int root_width = font.GetStringWidth(rootname); |
322 | 323 |
323 // We may have trimmed the path. | 324 // We may have trimmed the path. |
324 if (root_width + ext_width <= available_pixel_width) { | 325 if (root_width + ext_width <= available_pixel_width) { |
325 string16 elided_name = rootname + extension; | 326 string16 elided_name = rootname + extension; |
326 return base::i18n::GetDisplayStringInLTRDirectionality(elided_name); | 327 return base::i18n::GetDisplayStringInLTRDirectionality(elided_name); |
327 } | 328 } |
328 | 329 |
329 if (ext_width >= available_pixel_width) { | 330 if (ext_width >= available_pixel_width) { |
330 string16 elided_name = ElideText(rootname + extension, font, | 331 string16 elided_name = ElideText(rootname + extension, font, |
331 available_pixel_width, true); | 332 available_pixel_width, |
| 333 ui::ELIDE_IN_MIDDLE); |
332 return base::i18n::GetDisplayStringInLTRDirectionality(elided_name); | 334 return base::i18n::GetDisplayStringInLTRDirectionality(elided_name); |
333 } | 335 } |
334 | 336 |
335 int available_root_width = available_pixel_width - ext_width; | 337 int available_root_width = available_pixel_width - ext_width; |
336 string16 elided_name = | 338 string16 elided_name = |
337 ElideText(rootname, font, available_root_width, false); | 339 ElideText(rootname, font, available_root_width, ui::ELIDE_AT_END); |
338 elided_name += extension; | 340 elided_name += extension; |
339 return base::i18n::GetDisplayStringInLTRDirectionality(elided_name); | 341 return base::i18n::GetDisplayStringInLTRDirectionality(elided_name); |
340 } | 342 } |
341 | 343 |
342 // This function adds an ellipsis at the end of the text if the text | 344 // This function adds an ellipsis at the end of the text if the text |
343 // does not fit the given pixel width. | 345 // does not fit the given pixel width. |
344 string16 ElideText(const string16& text, | 346 string16 ElideText(const string16& text, |
345 const gfx::Font& font, | 347 const gfx::Font& font, |
346 int available_pixel_width, | 348 int available_pixel_width, |
347 bool elide_in_middle) { | 349 ElideBehavior elide_behavior) { |
348 if (text.empty()) | 350 if (text.empty()) |
349 return text; | 351 return text; |
350 | 352 |
351 int current_text_pixel_width = font.GetStringWidth(text); | 353 int current_text_pixel_width = font.GetStringWidth(text); |
| 354 bool elide_in_middle = (elide_behavior == ui::ELIDE_IN_MIDDLE); |
| 355 bool insert_ellipsis = (elide_behavior != ui::TRUNCATE_AT_END); |
352 | 356 |
353 // Pango will return 0 width for absurdly long strings. Cut the string in | 357 // Pango will return 0 width for absurdly long strings. Cut the string in |
354 // half and try again. | 358 // half and try again. |
355 // This is caused by an int overflow in Pango (specifically, in | 359 // This is caused by an int overflow in Pango (specifically, in |
356 // pango_glyph_string_extents_range). It's actually more subtle than just | 360 // pango_glyph_string_extents_range). It's actually more subtle than just |
357 // returning 0, since on super absurdly long strings, the int can wrap and | 361 // returning 0, since on super absurdly long strings, the int can wrap and |
358 // return positive numbers again. Detecting that is probably not worth it | 362 // return positive numbers again. Detecting that is probably not worth it |
359 // (eliding way too much from a ridiculous string is probably still | 363 // (eliding way too much from a ridiculous string is probably still |
360 // ridiculous), but we should check other widths for bogus values as well. | 364 // ridiculous), but we should check other widths for bogus values as well. |
361 if (current_text_pixel_width <= 0 && !text.empty()) { | 365 if (current_text_pixel_width <= 0 && !text.empty()) { |
362 return ElideText(CutString(text, text.length() / 2, elide_in_middle, false), | 366 return ElideText(CutString(text, text.length() / 2, elide_in_middle, false), |
363 font, available_pixel_width, false); | 367 font, available_pixel_width, elide_behavior); |
364 } | 368 } |
365 | 369 |
366 if (current_text_pixel_width <= available_pixel_width) | 370 if (current_text_pixel_width <= available_pixel_width) |
367 return text; | 371 return text; |
368 | 372 |
369 if (font.GetStringWidth(UTF8ToUTF16(kEllipsis)) > available_pixel_width) | 373 if (font.GetStringWidth(UTF8ToUTF16(kEllipsis)) > available_pixel_width) |
370 return string16(); | 374 return string16(); |
371 | 375 |
372 // Use binary search to compute the elided text. | 376 // Use binary search to compute the elided text. |
373 size_t lo = 0; | 377 size_t lo = 0; |
374 size_t hi = text.length() - 1; | 378 size_t hi = text.length() - 1; |
375 for (size_t guess = (lo + hi) / 2; guess != lo; guess = (lo + hi) / 2) { | 379 size_t guess; |
| 380 for (guess = (lo + hi) / 2; lo <= hi; guess = (lo + hi) / 2) { |
376 // We check the length of the whole desired string at once to ensure we | 381 // We check the length of the whole desired string at once to ensure we |
377 // handle kerning/ligatures/etc. correctly. | 382 // handle kerning/ligatures/etc. correctly. |
378 int guess_length = font.GetStringWidth( | 383 string16 cut = CutString(text, guess, elide_in_middle, insert_ellipsis); |
379 CutString(text, guess, elide_in_middle, true)); | 384 int guess_length = font.GetStringWidth(cut); |
380 // Check again that we didn't hit a Pango width overflow. If so, cut the | 385 // Check again that we didn't hit a Pango width overflow. If so, cut the |
381 // current string in half and start over. | 386 // current string in half and start over. |
382 if (guess_length <= 0) { | 387 if (guess_length <= 0) { |
383 return ElideText(CutString(text, guess / 2, elide_in_middle, false), | 388 return ElideText(CutString(text, guess / 2, elide_in_middle, false), |
384 font, available_pixel_width, elide_in_middle); | 389 font, available_pixel_width, elide_behavior); |
385 } | 390 } |
386 if (guess_length > available_pixel_width) | 391 if (guess_length > available_pixel_width) |
387 hi = guess; | 392 hi = guess - 1; |
388 else | 393 else |
389 lo = guess; | 394 lo = guess + 1; |
390 } | 395 } |
391 | 396 |
392 return CutString(text, lo, elide_in_middle, true); | 397 return CutString(text, guess, elide_in_middle, insert_ellipsis); |
393 } | 398 } |
394 | 399 |
395 SortedDisplayURL::SortedDisplayURL(const GURL& url, | 400 SortedDisplayURL::SortedDisplayURL(const GURL& url, |
396 const std::string& languages) { | 401 const std::string& languages) { |
397 net::AppendFormattedHost(url, languages, &sort_host_); | 402 net::AppendFormattedHost(url, languages, &sort_host_); |
398 string16 host_minus_www = net::StripWWW(sort_host_); | 403 string16 host_minus_www = net::StripWWW(sort_host_); |
399 url_parse::Parsed parsed; | 404 url_parse::Parsed parsed; |
400 display_url_ = net::FormatUrl(url, languages, | 405 display_url_ = net::FormatUrl(url, languages, |
401 net::kFormatUrlOmitAll, net::UnescapeRule::SPACES, &parsed, &prefix_end_, | 406 net::kFormatUrlOmitAll, net::UnescapeRule::SPACES, &parsed, &prefix_end_, |
402 NULL); | 407 NULL); |
(...skipping 166 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
569 size_t current_col_; | 574 size_t current_col_; |
570 | 575 |
571 // True when we do whitespace to newline conversions ourselves. | 576 // True when we do whitespace to newline conversions ourselves. |
572 bool strict_; | 577 bool strict_; |
573 | 578 |
574 // True when some of the input has been truncated. | 579 // True when some of the input has been truncated. |
575 bool suppressed_; | 580 bool suppressed_; |
576 | 581 |
577 // String onto which the output is accumulated. | 582 // String onto which the output is accumulated. |
578 string16* output_; | 583 string16* output_; |
| 584 |
| 585 DISALLOW_COPY_AND_ASSIGN(RectangleString); |
579 }; | 586 }; |
580 | 587 |
581 void RectangleString::AddString(const string16& input) { | 588 void RectangleString::AddString(const string16& input) { |
582 base::i18n::BreakIterator lines(input, | 589 base::i18n::BreakIterator lines(input, |
583 base::i18n::BreakIterator::BREAK_NEWLINE); | 590 base::i18n::BreakIterator::BREAK_NEWLINE); |
584 if (lines.Init()) { | 591 if (lines.Init()) { |
585 while (lines.Advance()) | 592 while (lines.Advance()) |
586 AddLine(lines.GetString()); | 593 AddLine(lines.GetString()); |
587 } else { | 594 } else { |
588 NOTREACHED() << "BreakIterator (lines) init failed"; | 595 NOTREACHED() << "BreakIterator (lines) init failed"; |
(...skipping 141 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
730 index = char_iterator.getIndex(); | 737 index = char_iterator.getIndex(); |
731 } else { | 738 } else { |
732 // String has leading whitespace, return the elide string. | 739 // String has leading whitespace, return the elide string. |
733 return kElideString; | 740 return kElideString; |
734 } | 741 } |
735 } | 742 } |
736 return string.substr(0, index) + kElideString; | 743 return string.substr(0, index) + kElideString; |
737 } | 744 } |
738 | 745 |
739 } // namespace ui | 746 } // namespace ui |
OLD | NEW |