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 "character_composer.h" | 5 #include "character_composer.h" |
6 | 6 |
7 #include <cstdlib> | 7 #include <algorithm> |
8 #include <iterator> | |
8 | 9 |
9 #include "third_party/gtk+/gdk/gdkkeysyms.h" | 10 #include "third_party/gtk+/gdk/gdkkeysyms.h" |
11 #include "ui/base/gtk/gtk_integers.h" | |
10 | 12 |
11 namespace { | 13 namespace { |
12 | 14 |
13 inline int CompareSequenceValue(unsigned int key, uint16 value) { | 15 typedef std::vector<unsigned int> ComposeBufferType; |
14 return (key > value) ? 1 : ((key < value) ? -1 : 0); | 16 |
17 // An iterator class to apply std::lower_bound for composition table | |
18 class SequenceIterator : | |
19 public std::iterator<std::random_access_iterator_tag, const uint16*> { | |
20 public: | |
21 SequenceIterator() : ptr_(NULL), stride_(0) {} | |
22 SequenceIterator(const uint16* ptr, int stride) | |
23 : ptr_(ptr), stride_(stride) {} | |
24 | |
25 const uint16* ptr() const {return ptr_;} | |
26 int stride() const {return stride_;} | |
27 | |
28 SequenceIterator& operator++() { | |
29 ptr_ += stride_; | |
30 return *this; | |
31 } | |
32 SequenceIterator& operator--() { | |
33 ptr_ -= stride_; | |
34 return *this; | |
35 } | |
36 SequenceIterator& operator+=(int n) { | |
37 ptr_ += stride_*n; | |
38 return *this; | |
39 } | |
40 SequenceIterator& operator-=(int n) { | |
41 return (*this += -n); | |
42 } | |
43 | |
44 const uint16* operator*() const {return ptr_;} | |
45 | |
46 private: | |
47 const uint16* ptr_; | |
48 int stride_; | |
49 }; | |
50 | |
51 inline SequenceIterator operator+(const SequenceIterator& l, int r) { | |
52 return SequenceIterator(l) += r; | |
15 } | 53 } |
16 | 54 |
55 inline SequenceIterator operator-(const SequenceIterator& l, int r) { | |
56 return l + (-r); | |
57 } | |
58 | |
59 inline int operator-(const SequenceIterator& l, const SequenceIterator& r) { | |
60 const int d = l.ptr() - r.ptr(); | |
61 DCHECK(l.stride() == r.stride() && l.stride() > 0 && d%l.stride() == 0); | |
62 return d/l.stride(); | |
63 } | |
64 | |
65 inline bool operator==(const SequenceIterator& l, const SequenceIterator& r) { | |
66 DCHECK(l.stride() == r.stride()); | |
67 return l.ptr() == r.ptr(); | |
68 } | |
69 | |
70 inline bool operator!=(const SequenceIterator& l, const SequenceIterator& r) { | |
71 return !(l == r); | |
72 } | |
73 | |
74 inline bool operator<(const SequenceIterator& l, const SequenceIterator& r) { | |
75 DCHECK(l.stride() == r.stride()); | |
76 return l.ptr() < r.ptr(); | |
77 } | |
78 | |
79 inline bool operator>(const SequenceIterator& l, const SequenceIterator& r) { | |
80 return r < l; | |
81 } | |
82 | |
83 inline bool operator<=(const SequenceIterator& l, const SequenceIterator& r) { | |
84 return !(l > r); | |
85 } | |
86 | |
87 inline bool operator>=(const SequenceIterator& l, const SequenceIterator& r) { | |
brettw
2011/07/06 16:51:24
Are all these operators really necessary to make l
hashimoto
2011/07/07 07:05:32
I thought having all comparing operators is ration
| |
88 return !(l < r); | |
89 } | |
90 | |
91 | |
92 // A function to compare keycode value | |
93 inline int CompareSequenceValue(unsigned int l, unsigned int r) { | |
94 return (l > r) ? 1 : ((l < r) ? -1 : 0); | |
95 } | |
96 | |
97 // A template to make |CompareFunc| work like operator<. | |
98 // |CompareFunc| is required to implement a member function, | |
99 // int operator()(const ComposeBufferType& l, const uint16* r) const. | |
100 template<typename CompareFunc> | |
101 struct ComparatorAdoptor { | |
102 bool operator()(const ComposeBufferType& l, const uint16* r) const { | |
103 return CompareFunc()(l, r) == -1; | |
104 } | |
105 bool operator()(const uint16* l, const ComposeBufferType& r) const { | |
106 return CompareFunc()(r, l) == 1; | |
107 } | |
108 }; | |
109 | |
17 class ComposeChecker { | 110 class ComposeChecker { |
18 public: | 111 public: |
112 // This class does not take the ownership of |data|, |data| should be alive | |
113 // for the lifetime of the object. | |
114 // |data| is a pointer to the head of an array of | |
115 // length (|max_sequence_length| + 2)*|n_sequences|. | |
116 // Every (|max_sequence_length| + 2) elements of |data| represent an entry. | |
117 // First |max_sequence_length| elements of an entry is the sequecne which | |
118 // composes the character represented by the last two elements of the entry. | |
19 ComposeChecker(const uint16* data, int max_sequence_length, int n_sequences); | 119 ComposeChecker(const uint16* data, int max_sequence_length, int n_sequences); |
20 bool CheckSequence(const std::vector<unsigned int>& sequence, | 120 bool CheckSequence(const ComposeBufferType& sequence, |
21 uint32* composed_character) const; | 121 uint32* composed_character) const; |
22 | 122 |
23 private: | 123 private: |
24 static int CompareSequence(const void* key_void, const void* value_void); | 124 struct CompareSequence { |
125 int operator()(const ComposeBufferType& l, const uint16* r) const; | |
126 }; | |
25 | 127 |
128 // This class does not take the ownership of |data_|, | |
129 // the dtor does not delete |data_|. | |
26 const uint16* data_; | 130 const uint16* data_; |
27 int max_sequence_length_; | 131 int max_sequence_length_; |
28 int n_sequences_; | 132 int n_sequences_; |
29 int row_stride_; | 133 int row_stride_; |
30 | 134 |
31 DISALLOW_COPY_AND_ASSIGN(ComposeChecker); | 135 DISALLOW_COPY_AND_ASSIGN(ComposeChecker); |
32 }; | 136 }; |
33 | 137 |
34 ComposeChecker::ComposeChecker(const uint16* data, | 138 ComposeChecker::ComposeChecker(const uint16* data, |
35 int max_sequence_length, | 139 int max_sequence_length, |
36 int n_sequences) | 140 int n_sequences) |
37 : data_(data), | 141 : data_(data), |
38 max_sequence_length_(max_sequence_length), | 142 max_sequence_length_(max_sequence_length), |
39 n_sequences_(n_sequences), | 143 n_sequences_(n_sequences), |
40 row_stride_(max_sequence_length + 2) { | 144 row_stride_(max_sequence_length + 2) { |
41 } | 145 } |
42 | 146 |
43 bool ComposeChecker::CheckSequence( | 147 bool ComposeChecker::CheckSequence(const ComposeBufferType& sequence, |
44 const std::vector<unsigned int>& sequence, | 148 uint32* composed_character) const { |
45 uint32* composed_character) const { | |
46 const int sequence_length = sequence.size(); | 149 const int sequence_length = sequence.size(); |
47 if (sequence_length > max_sequence_length_) | 150 if (sequence_length > max_sequence_length_) |
48 return false; | 151 return false; |
49 // Find sequence in the table | 152 // Find sequence in the table. |
50 const uint16* found = static_cast<const uint16*>( | 153 const SequenceIterator begin(data_, row_stride_); |
51 bsearch(&sequence, data_, n_sequences_, | 154 const SequenceIterator end = begin + n_sequences_; |
52 sizeof(uint16)*row_stride_, CompareSequence)); | 155 const SequenceIterator found = std::lower_bound( |
53 if (!found) | 156 begin, end, sequence, ComparatorAdoptor<CompareSequence>()); |
157 if (found >= end || CompareSequence()(sequence, *found) != 0) | |
54 return false; | 158 return false; |
55 // Ensure |found| is pointing the first matching element | |
56 while (found > data_ && | |
57 CompareSequence(&sequence, found - row_stride_) == 0) | |
58 found -= row_stride_; | |
59 | 159 |
60 if (sequence_length == max_sequence_length_ || found[sequence_length] == 0) { | 160 if (sequence_length == max_sequence_length_ || |
61 // |found| is not partially matching. It's fully matching | 161 (*found)[sequence_length] == 0) { |
62 const uint16* data_end = data_ + row_stride_*n_sequences_; | 162 // |found| is not partially matching. It's fully matching. |
63 if (found + row_stride_ >= data_end || | 163 if (found + 1 >= end || |
64 CompareSequence(&sequence, found + row_stride_) != 0) { | 164 CompareSequence()(sequence, *(found + 1)) != 0) { |
65 // There is no composition longer than |found| which matches to |sequence| | 165 // There is no composition longer than |found| which matches to |
66 const uint32 value = (found[max_sequence_length_] << 16) | | 166 // |sequence|. |
67 found[max_sequence_length_ + 1]; | 167 const uint32 value = ((*found)[max_sequence_length_] << 16) | |
168 (*found)[max_sequence_length_ + 1]; | |
68 *composed_character = value; | 169 *composed_character = value; |
69 } | 170 } |
70 } | 171 } |
71 return true; | 172 return true; |
72 } | 173 } |
73 | 174 |
74 // static | 175 int ComposeChecker::CompareSequence::operator()(const ComposeBufferType& l, |
75 int ComposeChecker::CompareSequence(const void* key_void, | 176 const uint16* r) const { |
76 const void* value_void) { | 177 for(size_t i = 0; i < l.size(); ++i) { |
77 typedef std::vector<unsigned int> KeyType; | 178 const int compare_result = CompareSequenceValue(l[i], r[i]); |
78 const KeyType& key = *static_cast<const KeyType*>(key_void); | |
79 const uint16* value = static_cast<const uint16*>(value_void); | |
80 | |
81 for(size_t i = 0; i < key.size(); ++i) { | |
82 const int compare_result = CompareSequenceValue(key[i], value[i]); | |
83 if(compare_result) | 179 if(compare_result) |
84 return compare_result; | 180 return compare_result; |
85 } | 181 } |
86 return 0; | 182 return 0; |
87 } | 183 } |
88 | 184 |
89 | 185 |
90 class ComposeCheckerWithCompactTable { | 186 class ComposeCheckerWithCompactTable { |
91 public: | 187 public: |
188 // This class does not take the ownership of |data|, |data| should be alive | |
189 // for the lifetime of the object. | |
190 // First |index_size|*|index_stride| elements of |data| are an index table. | |
191 // Every |index_stride| elements of an index table are an index entry. | |
192 // If you are checking with a sequence of length N beginning with character C, | |
193 // you have to find an index entry whose first element is C, then get the N-th | |
194 // element of the index entry as the index. | |
195 // The index is pointing the element of |data| where the composition table for | |
196 // sequences of length N beginning with C is placed. | |
197 | |
92 ComposeCheckerWithCompactTable(const uint16* data, | 198 ComposeCheckerWithCompactTable(const uint16* data, |
93 int max_sequence_length, | 199 int max_sequence_length, |
94 int index_size, | 200 int index_size, |
95 int index_stride); | 201 int index_stride); |
96 bool CheckSequence(const std::vector<unsigned int>& sequence, | 202 bool CheckSequence(const ComposeBufferType& sequence, |
97 uint32* composed_character) const; | 203 uint32* composed_character) const; |
98 | 204 |
99 private: | 205 private: |
100 static int CompareSequenceFront(const void* key_void, const void* value_void); | 206 struct CompareSequenceFront { |
101 static int CompareSequenceSkipFront(const void* key_void, | 207 int operator()(const ComposeBufferType& l, const uint16* r) const; |
102 const void* value_void); | 208 }; |
209 struct CompareSequenceSkipFront { | |
210 int operator()(const ComposeBufferType& l, const uint16* r) const; | |
211 }; | |
103 | 212 |
213 // This class does not take the ownership of |data_|, | |
214 // the dtor does not delete |data_|. | |
104 const uint16* data_; | 215 const uint16* data_; |
105 int max_sequence_length_; | 216 int max_sequence_length_; |
106 int index_size_; | 217 int index_size_; |
107 int index_stride_; | 218 int index_stride_; |
108 }; | 219 }; |
109 | 220 |
110 ComposeCheckerWithCompactTable::ComposeCheckerWithCompactTable( | 221 ComposeCheckerWithCompactTable::ComposeCheckerWithCompactTable( |
111 const uint16* data, | 222 const uint16* data, |
112 int max_sequence_length, | 223 int max_sequence_length, |
113 int index_size, | 224 int index_size, |
114 int index_stride) | 225 int index_stride) |
115 : data_(data), | 226 : data_(data), |
116 max_sequence_length_(max_sequence_length), | 227 max_sequence_length_(max_sequence_length), |
117 index_size_(index_size), | 228 index_size_(index_size), |
118 index_stride_(index_stride) { | 229 index_stride_(index_stride) { |
119 } | 230 } |
120 | 231 |
121 bool ComposeCheckerWithCompactTable::CheckSequence( | 232 bool ComposeCheckerWithCompactTable::CheckSequence( |
122 const std::vector<unsigned int>& sequence, | 233 const ComposeBufferType& sequence, |
123 uint32* composed_character) const { | 234 uint32* composed_character) const { |
124 const int compose_length = sequence.size(); | 235 const int compose_length = sequence.size(); |
125 if (compose_length > max_sequence_length_) | 236 if (compose_length > max_sequence_length_) |
126 return false; | 237 return false; |
127 // Find corresponding index for the first keypress | 238 // Find corresponding index for the first keypress. |
128 const uint16* index = static_cast<const uint16*>( | 239 const SequenceIterator index_begin(data_, index_stride_); |
129 bsearch(&sequence, data_, index_size_, | 240 const SequenceIterator index_end = index_begin + index_size_; |
130 sizeof(uint16)*index_stride_, CompareSequenceFront)); | 241 const SequenceIterator index = |
131 if (!index) | 242 std::lower_bound(index_begin, index_end, sequence, |
243 ComparatorAdoptor<CompareSequenceFront>()); | |
244 if (index >= index_end || CompareSequenceFront()(sequence, *index) != 0) | |
132 return false; | 245 return false; |
133 if (compose_length == 1) | 246 if (compose_length == 1) |
134 return true; | 247 return true; |
135 // Check for composition sequences | 248 // Check for composition sequences. |
136 for (int length = compose_length - 1; length < max_sequence_length_; | 249 for (int length = compose_length - 1; length < max_sequence_length_; |
137 ++length) { | 250 ++length) { |
138 const uint16* table = data_ + index[length]; | 251 const uint16* table = data_ + (*index)[length]; |
139 const uint16* table_next = data_ + index[length + 1]; | 252 const uint16* table_next = data_ + (*index)[length + 1]; |
140 if (table_next > table) { | 253 if (table_next > table) { |
141 // There are composition sequences for this |length| | 254 // There are composition sequences for this |length|. |
142 const int row_stride = length + 1; | 255 const int row_stride = length + 1; |
143 const int n_sequences = (table_next - table)/row_stride; | 256 const int n_sequences = (table_next - table)/row_stride; |
144 const uint16* seq = static_cast<const uint16*>( | 257 const SequenceIterator table_begin(table, row_stride); |
145 bsearch(&sequence, table, n_sequences, | 258 const SequenceIterator table_end = table_begin + n_sequences; |
146 sizeof(uint16)*row_stride, CompareSequenceSkipFront)); | 259 const SequenceIterator found = |
147 if (seq) { | 260 std::lower_bound(table_begin, table_end, sequence, |
148 if (length == compose_length - 1) // exact match | 261 ComparatorAdoptor<CompareSequenceSkipFront>()); |
149 *composed_character = seq[length]; | 262 if (found < table_end && |
263 CompareSequenceSkipFront()(sequence, *found) == 0) { | |
264 if (length == compose_length - 1) // Exact match. | |
265 *composed_character = (*found)[length]; | |
150 return true; | 266 return true; |
151 } | 267 } |
152 } | 268 } |
153 } | 269 } |
154 return false; | 270 return false; |
155 } | 271 } |
156 | 272 |
157 // static | 273 int ComposeCheckerWithCompactTable::CompareSequenceFront::operator()( |
158 int ComposeCheckerWithCompactTable::CompareSequenceFront( | 274 const ComposeBufferType& l, const uint16* r) const { |
159 const void* key_void, | 275 return CompareSequenceValue(l[0], r[0]); |
160 const void* value_void) { | |
161 typedef std::vector<unsigned int> KeyType; | |
162 const KeyType& key = *static_cast<const KeyType*>(key_void); | |
163 const uint16* value = static_cast<const uint16*>(value_void); | |
164 | |
165 return CompareSequenceValue(key[0], value[0]); | |
166 } | 276 } |
167 | 277 |
168 // static | 278 int ComposeCheckerWithCompactTable::CompareSequenceSkipFront::operator()( |
169 int ComposeCheckerWithCompactTable::CompareSequenceSkipFront( | 279 const ComposeBufferType& l, const uint16* r) const { |
170 const void* key_void, | 280 for(size_t i = 1; i < l.size(); ++i) { |
171 const void* value_void) { | 281 const int compare_result = CompareSequenceValue(l[i], r[i - 1]); |
172 typedef std::vector<unsigned int> KeyType; | |
173 const KeyType& key = *static_cast<const KeyType*>(key_void); | |
174 const uint16* value = static_cast<const uint16*>(value_void); | |
175 | |
176 for(size_t i = 1; i < key.size(); ++i) { | |
177 const int compare_result = CompareSequenceValue(key[i], value[i - 1]); | |
178 if(compare_result) | 282 if(compare_result) |
179 return compare_result; | 283 return compare_result; |
180 } | 284 } |
181 return 0; | 285 return 0; |
182 } | 286 } |
183 | 287 |
184 // Main table | 288 |
185 typedef uint16 guint16; | 289 // Main table. |
290 | |
291 // This file is included here intentionally, instead of the top of the file, | |
292 // because including this file outside the anonymous namespace will define a | |
293 // global constant and contaminate the global namespace. | |
186 #include "third_party/gtk+/gtk/gtkimcontextsimpleseqs.h" | 294 #include "third_party/gtk+/gtk/gtkimcontextsimpleseqs.h" |
187 | 295 |
188 // Additional table | 296 |
297 // Additional table. | |
189 | 298 |
190 // The difference between this and the default input method is the handling | 299 // The difference between this and the default input method is the handling |
191 // of C+acute - this method produces C WITH CEDILLA rather than C WITH ACUTE. | 300 // of C+acute - this method produces C WITH CEDILLA rather than C WITH ACUTE. |
192 // For languages that use CCedilla and not acute, this is the preferred mapping, | 301 // For languages that use CCedilla and not acute, this is the preferred mapping, |
193 // and is particularly important for pt_BR, where the us-intl keyboard is | 302 // and is particularly important for pt_BR, where the us-intl keyboard is |
194 // used extensively. | 303 // used extensively. |
195 | 304 |
196 const uint16 cedilla_compose_seqs[] = { | 305 const uint16 cedilla_compose_seqs[] = { |
197 // LATIN_CAPITAL_LETTER_C_WITH_CEDILLA | 306 // LATIN_CAPITAL_LETTER_C_WITH_CEDILLA |
198 GDK_KEY_dead_acute, GDK_KEY_C, 0, 0, 0, 0x00C7, | 307 GDK_KEY_dead_acute, GDK_KEY_C, 0, 0, 0, 0x00C7, |
(...skipping 26 matching lines...) Expand all Loading... | |
225 case GDK_KEY_Hyper_L: | 334 case GDK_KEY_Hyper_L: |
226 case GDK_KEY_Hyper_R: | 335 case GDK_KEY_Hyper_R: |
227 case GDK_KEY_Mode_switch: | 336 case GDK_KEY_Mode_switch: |
228 case GDK_KEY_ISO_Level3_Shift: | 337 case GDK_KEY_ISO_Level3_Shift: |
229 return true; | 338 return true; |
230 default: | 339 default: |
231 return false; | 340 return false; |
232 } | 341 } |
233 } | 342 } |
234 | 343 |
235 bool CheckCharacterComposeTable(const std::vector<unsigned int>& sequence, | 344 bool CheckCharacterComposeTable(const ComposeBufferType& sequence, |
236 uint32* composed_character) { | 345 uint32* composed_character) { |
237 // Check cedilla compose table | 346 // Check cedilla compose table. |
238 const ComposeChecker kCedillaComposeChecker( | 347 const ComposeChecker kCedillaComposeChecker( |
239 cedilla_compose_seqs, 4, arraysize(cedilla_compose_seqs)/(4 + 2)); | 348 cedilla_compose_seqs, 4, arraysize(cedilla_compose_seqs)/(4 + 2)); |
240 if (kCedillaComposeChecker.CheckSequence(sequence, composed_character)) | 349 if (kCedillaComposeChecker.CheckSequence(sequence, composed_character)) |
241 return true; | 350 return true; |
242 | 351 |
243 // Check main compose table | 352 // Check main compose table. |
244 const ComposeCheckerWithCompactTable kMainComposeChecker( | 353 const ComposeCheckerWithCompactTable kMainComposeChecker( |
245 gtk_compose_seqs_compact, 5, 24, 6); | 354 gtk_compose_seqs_compact, 5, 24, 6); |
246 if (kMainComposeChecker.CheckSequence(sequence, composed_character)) | 355 if (kMainComposeChecker.CheckSequence(sequence, composed_character)) |
247 return true; | 356 return true; |
248 | 357 |
249 return false; | 358 return false; |
250 } | 359 } |
251 | 360 |
252 } // anonymous namespace | 361 } // namespace |
253 | 362 |
254 namespace views { | 363 namespace views { |
255 | 364 |
256 CharacterComposer::CharacterComposer() {} | 365 CharacterComposer::CharacterComposer() {} |
257 | 366 |
258 CharacterComposer::~CharacterComposer() {} | 367 CharacterComposer::~CharacterComposer() {} |
259 | 368 |
260 void CharacterComposer::Reset() { | 369 void CharacterComposer::Reset() { |
261 compose_buffer_.clear(); | 370 compose_buffer_.clear(); |
262 composed_character_.clear(); | 371 composed_character_.clear(); |
263 } | 372 } |
264 | 373 |
265 bool CharacterComposer::FilterKeyPress(unsigned int keycode) { | 374 bool CharacterComposer::FilterKeyPress(unsigned int keycode) { |
266 if(KeypressShouldBeIgnored(keycode)) | 375 if(KeypressShouldBeIgnored(keycode)) |
267 return false; | 376 return false; |
268 | 377 |
269 compose_buffer_.push_back(keycode); | 378 compose_buffer_.push_back(keycode); |
270 | 379 |
271 // Check compose table | 380 // Check compose table. |
272 composed_character_.clear(); | 381 composed_character_.clear(); |
273 uint32 composed_character_utf32 = 0; | 382 uint32 composed_character_utf32 = 0; |
274 if (CheckCharacterComposeTable(compose_buffer_, &composed_character_utf32)) { | 383 if (CheckCharacterComposeTable(compose_buffer_, &composed_character_utf32)) { |
275 // Key press is recognized as a part of composition | 384 // Key press is recognized as a part of composition. |
276 if (composed_character_utf32 !=0) { | 385 if (composed_character_utf32 !=0) { |
277 // We get a composed character | 386 // We get a composed character. |
278 compose_buffer_.clear(); | 387 compose_buffer_.clear(); |
279 // We assume that composed character is in BMP | 388 // We assume that composed character is in BMP. |
280 if (composed_character_utf32 <= 0xffff) | 389 if (composed_character_utf32 <= 0xffff) |
281 composed_character_ += static_cast<char16>(composed_character_utf32); | 390 composed_character_ += static_cast<char16>(composed_character_utf32); |
282 } | 391 } |
283 return true; | 392 return true; |
284 } | 393 } |
285 // Key press is not a part of composition | 394 // Key press is not a part of composition. |
286 compose_buffer_.pop_back(); // remove the keypress added this time | 395 compose_buffer_.pop_back(); // Remove the keypress added this time. |
287 if (!compose_buffer_.empty()) { | 396 if (!compose_buffer_.empty()) { |
288 compose_buffer_.clear(); | 397 compose_buffer_.clear(); |
289 return true; | 398 return true; |
290 } | 399 } |
291 return false; | 400 return false; |
292 } | 401 } |
293 | 402 |
294 } // namespace views | 403 } // namespace views |
OLD | NEW |