Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(220)

Side by Side Diff: third_party/google_input_tools/src/chrome/os/inputview/adapter.js

Issue 899673003: Uprev Google Input Tools. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Created 5 years, 10 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
1 // Copyright 2014 The ChromeOS IME Authors. All Rights Reserved. 1 // Copyright 2014 The ChromeOS IME Authors. All Rights Reserved.
2 // limitations under the License. 2 // limitations under the License.
3 // See the License for the specific language governing permissions and 3 // See the License for the specific language governing permissions and
4 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 4 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
5 // distributed under the License is distributed on an "AS-IS" BASIS, 5 // distributed under the License is distributed on an "AS-IS" BASIS,
6 // Unless required by applicable law or agreed to in writing, software 6 // Unless required by applicable law or agreed to in writing, software
7 // 7 //
8 // http://www.apache.org/licenses/LICENSE-2.0 8 // http://www.apache.org/licenses/LICENSE-2.0
9 // 9 //
10 // You may obtain a copy of the License at 10 // You may obtain a copy of the License at
11 // you may not use this file except in compliance with the License. 11 // you may not use this file except in compliance with the License.
12 // Licensed under the Apache License, Version 2.0 (the "License"); 12 // Licensed under the Apache License, Version 2.0 (the "License");
13 // 13 //
14 goog.provide('i18n.input.chrome.inputview.Adapter'); 14 goog.provide('i18n.input.chrome.inputview.Adapter');
15 15
16 goog.require('goog.events.Event'); 16 goog.require('goog.events.Event');
17 goog.require('goog.events.EventHandler'); 17 goog.require('goog.events.EventHandler');
18 goog.require('goog.events.EventTarget'); 18 goog.require('goog.events.EventTarget');
19 goog.require('goog.events.EventType'); 19 goog.require('goog.events.EventType');
20 goog.require('goog.object'); 20 goog.require('goog.object');
21 goog.require('i18n.input.chrome.DataSource'); 21 goog.require('i18n.input.chrome.DataSource');
22 goog.require('i18n.input.chrome.inputview.GlobalFlags');
22 goog.require('i18n.input.chrome.inputview.ReadyState'); 23 goog.require('i18n.input.chrome.inputview.ReadyState');
23 goog.require('i18n.input.chrome.inputview.StateType'); 24 goog.require('i18n.input.chrome.inputview.StateType');
24 goog.require('i18n.input.chrome.inputview.events.EventType'); 25 goog.require('i18n.input.chrome.inputview.events.EventType');
25 goog.require('i18n.input.chrome.inputview.events.SurroundingTextChangedEvent'); 26 goog.require('i18n.input.chrome.inputview.events.SurroundingTextChangedEvent');
26 goog.require('i18n.input.chrome.message'); 27 goog.require('i18n.input.chrome.message');
27 goog.require('i18n.input.chrome.message.ContextType'); 28 goog.require('i18n.input.chrome.message.ContextType');
28 goog.require('i18n.input.chrome.message.Event'); 29 goog.require('i18n.input.chrome.message.Event');
29 goog.require('i18n.input.chrome.message.Name'); 30 goog.require('i18n.input.chrome.message.Name');
30 goog.require('i18n.input.chrome.message.Type'); 31 goog.require('i18n.input.chrome.message.Type');
31 32
(...skipping 48 matching lines...) Expand 10 before | Expand all | Expand 10 after
80 listen(window, goog.events.EventType.RESIZE, this.onVisibilityChange_); 81 listen(window, goog.events.EventType.RESIZE, this.onVisibilityChange_);
81 82
82 // Notifies the initial visibility change message to background. 83 // Notifies the initial visibility change message to background.
83 this.onVisibilityChange_(); 84 this.onVisibilityChange_();
84 }; 85 };
85 goog.inherits(i18n.input.chrome.inputview.Adapter, 86 goog.inherits(i18n.input.chrome.inputview.Adapter,
86 goog.events.EventTarget); 87 goog.events.EventTarget);
87 var Adapter = i18n.input.chrome.inputview.Adapter; 88 var Adapter = i18n.input.chrome.inputview.Adapter;
88 89
89 90
91 /**
92 * URL prefixes of common Google sites.
93 *
94 * @enum {string}
95 */
96 Adapter.GoogleSites = {
97 // TODO: Add support for spreadsheets.
98 DOCS: 'https://docs.google.com/document/d'
99 };
100
101
90 /** @type {boolean} */ 102 /** @type {boolean} */
91 Adapter.prototype.isA11yMode = false; 103 Adapter.prototype.isA11yMode = false;
92 104
93 105
94 /** @type {boolean} */ 106 /** @type {boolean} */
95 Adapter.prototype.isExperimental = false; 107 Adapter.prototype.isExperimental = false;
96 108
97 109
98 /** @type {boolean} */ 110 /** @type {boolean} */
111 Adapter.prototype.isVoiceInputEnabled = true;
112
113
114 /** @type {boolean} */
99 Adapter.prototype.showGlobeKey = false; 115 Adapter.prototype.showGlobeKey = false;
100 116
101
102 /** @type {string} */ 117 /** @type {string} */
103 Adapter.prototype.contextType = ContextType.DEFAULT; 118 Adapter.prototype.contextType = ContextType.DEFAULT;
104 119
105 120
106 /** @type {string} */ 121 /** @type {string} */
107 Adapter.prototype.screen = ''; 122 Adapter.prototype.screen = '';
108 123
109 124
110 /** @type {boolean} */ 125 /** @type {boolean} */
111 Adapter.prototype.isChromeVoxOn = false; 126 Adapter.prototype.isChromeVoxOn = false;
(...skipping 16 matching lines...) Expand all
128 143
129 144
130 /** 145 /**
131 * Callback for updating settings. 146 * Callback for updating settings.
132 * 147 *
133 * @param {!Object} message . 148 * @param {!Object} message .
134 * @private 149 * @private
135 */ 150 */
136 Adapter.prototype.onUpdateSettings_ = function(message) { 151 Adapter.prototype.onUpdateSettings_ = function(message) {
137 this.screen = message[Name.SCREEN]; 152 this.screen = message[Name.SCREEN];
153 this.queryCurrentSite();
138 this.contextType = /** @type {string} */ (message[Name.CONTEXT_TYPE]); 154 this.contextType = /** @type {string} */ (message[Name.CONTEXT_TYPE]);
139 // Resets the flag, since when inputview receive the update setting response, 155 // Resets the flag, since when inputview receive the update setting response,
140 // it means the background switching is done. 156 // it means the background switching is done.
141 this.isBgControllerSwitching_ = false; 157 this.isBgControllerSwitching_ = false;
142 this.dispatchEvent(new i18n.input.chrome.message.Event(Type.UPDATE_SETTINGS, 158 this.dispatchEvent(new i18n.input.chrome.message.Event(Type.UPDATE_SETTINGS,
143 message)); 159 message));
144 }; 160 };
145 161
146 162
147 /** 163 /**
(...skipping 15 matching lines...) Expand all
163 }; 179 };
164 180
165 181
166 /** 182 /**
167 * Simulates to send 'keydown' and 'keyup' event. 183 * Simulates to send 'keydown' and 'keyup' event.
168 * 184 *
169 * @param {string} key 185 * @param {string} key
170 * @param {string} code 186 * @param {string} code
171 * @param {number=} opt_keyCode The key code. 187 * @param {number=} opt_keyCode The key code.
172 * @param {!Object=} opt_spatialData . 188 * @param {!Object=} opt_spatialData .
189 * @param {!Object.<{ctrl: boolean, shift: boolean}>=} opt_modifiers .
173 */ 190 */
174 Adapter.prototype.sendKeyDownAndUpEvent = function(key, code, opt_keyCode, 191 Adapter.prototype.sendKeyDownAndUpEvent = function(key, code, opt_keyCode,
175 opt_spatialData) { 192 opt_spatialData, opt_modifiers) {
176 this.sendKeyEvent_([ 193 this.sendKeyEvent_([
177 this.generateKeyboardEvent_( 194 this.generateKeyboardEvent_(
178 goog.events.EventType.KEYDOWN, key, code, opt_keyCode, opt_spatialData), 195 goog.events.EventType.KEYDOWN,
196 key,
197 code,
198 opt_keyCode,
199 opt_spatialData,
200 opt_modifiers),
179 this.generateKeyboardEvent_( 201 this.generateKeyboardEvent_(
180 goog.events.EventType.KEYUP, key, code, opt_keyCode, opt_spatialData) 202 goog.events.EventType.KEYUP,
203 key,
204 code,
205 opt_keyCode,
206 opt_spatialData,
207 opt_modifiers)
181 ]); 208 ]);
182 }; 209 };
183 210
184 211
185 /** 212 /**
186 * Simulates to send 'keydown' event. 213 * Simulates to send 'keydown' event.
187 * 214 *
188 * @param {string} key 215 * @param {string} key
189 * @param {string} code 216 * @param {string} code
190 * @param {number=} opt_keyCode The key code. 217 * @param {number=} opt_keyCode The key code.
(...skipping 36 matching lines...) Expand 10 before | Expand all | Expand 10 after
227 254
228 255
229 /** 256 /**
230 * Generates a {@code ChromeKeyboardEvent} by given values. 257 * Generates a {@code ChromeKeyboardEvent} by given values.
231 * 258 *
232 * @param {string} type . 259 * @param {string} type .
233 * @param {string} key The key. 260 * @param {string} key The key.
234 * @param {string} code The code. 261 * @param {string} code The code.
235 * @param {number=} opt_keyCode The key code. 262 * @param {number=} opt_keyCode The key code.
236 * @param {!Object=} opt_spatialData . 263 * @param {!Object=} opt_spatialData .
264 * @param {!Object.<{ctrl: boolean, shift: boolean}>=} opt_modifiers .
237 * @return {!Object.<string, string|boolean>} 265 * @return {!Object.<string, string|boolean>}
238 * @private 266 * @private
239 */ 267 */
240 Adapter.prototype.generateKeyboardEvent_ = function( 268 Adapter.prototype.generateKeyboardEvent_ = function(
241 type, key, code, opt_keyCode, opt_spatialData) { 269 type, key, code, opt_keyCode, opt_spatialData, opt_modifiers) {
242 var StateType = i18n.input.chrome.inputview.StateType; 270 var StateType = i18n.input.chrome.inputview.StateType;
243 var ctrl = !!this.modifierState_[StateType.CTRL]; 271 var ctrl = !!this.modifierState_[StateType.CTRL];
244 var alt = !!this.modifierState_[StateType.ALT]; 272 var alt = !!this.modifierState_[StateType.ALT];
273 var shift = !!this.modifierState_[StateType.SHIFT];
274
275 if (opt_modifiers) {
276 if (opt_modifiers.ctrl)
277 ctrl = opt_modifiers.ctrl;
278 if (opt_modifiers.shift)
279 shift = opt_modifiers.shift;
280 }
245 281
246 if (ctrl || alt) { 282 if (ctrl || alt) {
247 key = ''; 283 key = '';
248 } 284 }
249 var result = { 285 var result = {
250 'type': type, 286 'type': type,
251 'key': key, 287 'key': key,
252 'code': code, 288 'code': code,
253 'keyCode': opt_keyCode || 0, 289 'keyCode': opt_keyCode || 0,
254 'spatialData': opt_spatialData 290 'spatialData': opt_spatialData
255 }; 291 };
256 292
257 result['altKey'] = alt; 293 result['altKey'] = alt;
258 result['ctrlKey'] = ctrl; 294 result['ctrlKey'] = ctrl;
259 result['shiftKey'] = !!this.modifierState_[StateType.SHIFT]; 295 result['shiftKey'] = shift;
260 result['capsLock'] = !!this.modifierState_[StateType.CAPSLOCK]; 296 result['capsLock'] = !!this.modifierState_[StateType.CAPSLOCK];
261 297
262 return result; 298 return result;
263 }; 299 };
264 300
265 301
266 /** 302 /**
267 * Callback when surrounding text is changed. 303 * Callback when surrounding text is changed.
268 * 304 *
269 * @param {string} text . 305 * @param {string} text .
306 * @param {number} anchor .
307 * @param {number} focus .
270 * @private 308 * @private
271 */ 309 */
272 Adapter.prototype.onSurroundingTextChanged_ = function(text) { 310 Adapter.prototype.onSurroundingTextChanged_ = function(text, anchor, focus) {
273 this.textBeforeCursor = text; 311 this.textBeforeCursor = text;
274 this.dispatchEvent(new i18n.input.chrome.inputview.events. 312 this.dispatchEvent(new i18n.input.chrome.inputview.events.
275 SurroundingTextChangedEvent(this.textBeforeCursor)); 313 SurroundingTextChangedEvent(this.textBeforeCursor, anchor, focus));
276 }; 314 };
277 315
278 316
279 /** 317 /**
280 * Gets the context. 318 * Gets the context.
281 * 319 *
282 * @return {string} . 320 * @return {string} .
283 */ 321 */
284 Adapter.prototype.getContext = function() { 322 Adapter.prototype.getContext = function() {
285 var matches = this.textBeforeCursor.match(/([a-zA-Z'-Ḁ-ỹÀ-ȳ]+)\s+$/); 323 var matches = this.textBeforeCursor.match(/([a-zA-Z'-Ḁ-ỹÀ-ȳ]+)\s+$/);
(...skipping 18 matching lines...) Expand all
304 * True if it is a password box. 342 * True if it is a password box.
305 * 343 *
306 * @return {boolean} . 344 * @return {boolean} .
307 */ 345 */
308 Adapter.prototype.isPasswordBox = function() { 346 Adapter.prototype.isPasswordBox = function() {
309 return this.contextType == 'password'; 347 return this.contextType == 'password';
310 }; 348 };
311 349
312 350
313 /** 351 /**
352 * True to enable gesture deletion.
353 *
354 * @return {boolean}
355 */
356 Adapter.prototype.isGestureDeletionEnabled = function() {
357 // TODO: Omni bar sends wrong anchor/focus when autocompleting
358 // URLs. Re-enable when that is fixed.
359 if (this.contextType == ContextType.URL) {
360 return false;
361 }
362 return this.isGestureEdittingEnabled();
363 };
364
365
366 /**
367 * True to enable gesture editting.
368 *
369 * @return {boolean}
370 */
371 Adapter.prototype.isGestureEdittingEnabled = function() {
372 return this.isExperimental;
373 };
374
375
376 /**
314 * Callback when blurs in the context. 377 * Callback when blurs in the context.
315 * 378 *
316 * @private 379 * @private
317 */ 380 */
318 Adapter.prototype.onContextBlur_ = function() { 381 Adapter.prototype.onContextBlur_ = function() {
319 this.contextType = ''; 382 this.contextType = '';
320 this.dispatchEvent(new goog.events.Event(i18n.input.chrome.inputview.events. 383 this.dispatchEvent(new goog.events.Event(i18n.input.chrome.inputview.events.
321 EventType.CONTEXT_BLUR)); 384 EventType.CONTEXT_BLUR));
322 }; 385 };
323 386
324 387
325 /** 388 /**
389 * Asynchronously queries the current site.
390 */
391 Adapter.prototype.queryCurrentSite = function() {
392 var adapter = this;
393 var criteria = {'active': true, 'lastFocusedWindow': true};
394 if (chrome && chrome.tabs) {
395 chrome.tabs.query(criteria, function(tabs) {
396 tabs[0] && adapter.setCurrentSite_(tabs[0].url);
397 });
398 }
399 };
400
401
402 /**
403 * Sets the current context URL.
404 *
405 * @param {string} url .
406 * @private
407 */
408 Adapter.prototype.setCurrentSite_ = function(url) {
409 if (url != this.currentSite_) {
410 this.currentSite_ = url;
411 this.dispatchEvent(new goog.events.Event(
412 i18n.input.chrome.inputview.events.EventType.URL_CHANGED));
413 }
414 };
415
416
417 /**
418 * Returns whether the current context is Google Documents.
419 *
420 * @return {boolean} .
421 */
422 Adapter.prototype.isGoogleDocument = function() {
423 return this.currentSite_ &&
424 this.currentSite_.lastIndexOf(Adapter.GoogleSites.DOCS) === 0;
425 };
426
427
428 /**
326 * Callback when focus on a context. 429 * Callback when focus on a context.
327 * 430 *
328 * @param {!Object<string, *>} message . 431 * @param {!Object<string, *>} message .
329 * @private 432 * @private
330 */ 433 */
331 Adapter.prototype.onContextFocus_ = function(message) { 434 Adapter.prototype.onContextFocus_ = function(message) {
435 // URL might have changed.
436 this.queryCurrentSite();
437
332 this.contextType = /** @type {string} */ (message[Name.CONTEXT_TYPE]); 438 this.contextType = /** @type {string} */ (message[Name.CONTEXT_TYPE]);
333 this.dispatchEvent(new goog.events.Event( 439 this.dispatchEvent(new goog.events.Event(
334 i18n.input.chrome.inputview.events.EventType.CONTEXT_FOCUS)); 440 i18n.input.chrome.inputview.events.EventType.CONTEXT_FOCUS));
335 }; 441 };
336 442
337 443
338 /** 444 /**
339 * Initializes the communication to background page. 445 * Initializes the communication to background page.
340 * 446 *
341 * @private 447 * @private
(...skipping 13 matching lines...) Expand all
355 * @param {string} languageCode The language code. 461 * @param {string} languageCode The language code.
356 */ 462 */
357 Adapter.prototype.initialize = function(languageCode) { 463 Adapter.prototype.initialize = function(languageCode) {
358 if (chrome.accessibilityFeatures && 464 if (chrome.accessibilityFeatures &&
359 chrome.accessibilityFeatures.spokenFeedback) { 465 chrome.accessibilityFeatures.spokenFeedback) {
360 chrome.accessibilityFeatures.spokenFeedback.get({}, (function(details) { 466 chrome.accessibilityFeatures.spokenFeedback.get({}, (function(details) {
361 this.isChromeVoxOn = details['value']; 467 this.isChromeVoxOn = details['value'];
362 }).bind(this)); 468 }).bind(this));
363 chrome.accessibilityFeatures.spokenFeedback.onChange.addListener((function( 469 chrome.accessibilityFeatures.spokenFeedback.onChange.addListener((function(
364 details) { 470 details) {
471 if (!this.isChromeVoxOn && details['value']) {
472 this.dispatchEvent(new goog.events.Event(
473 i18n.input.chrome.inputview.events.EventType.REFRESH));
474 }
365 this.isChromeVoxOn = details['value']; 475 this.isChromeVoxOn = details['value'];
366 }).bind(this)); 476 }).bind(this));
367 } 477 }
368 478
369 this.initBackground_(); 479 this.initBackground_();
370 480
371 var StateType = i18n.input.chrome.inputview.ReadyState.StateType; 481 var StateType = i18n.input.chrome.inputview.ReadyState.StateType;
372 if (window.inputview) { 482 if (window.inputview) {
373 inputview.getKeyboardConfig((function(config) { 483 inputview.getKeyboardConfig((function(config) {
374 this.isA11yMode = !!config['a11ymode']; 484 this.isA11yMode = !!config['a11ymode'];
375 this.isExperimental = !!config['experimental']; 485 this.isExperimental = !!config['experimental'];
376 this.readyState_.markStateReady(StateType.KEYBOARD_CONFIG_READY); 486 this.readyState_.markStateReady(StateType.KEYBOARD_CONFIG_READY);
377 this.maybeDispatchSettingsReadyEvent_(); 487 this.maybeDispatchSettingsReadyEvent_();
378 }).bind(this)); 488 }).bind(this));
379 inputview.getInputMethods((function(inputMethods) { 489 inputview.getInputMethods((function(inputMethods) {
380 // Only show globe key to switching between IMEs when there are more 490 // Only show globe key to switching between IMEs when there are more
381 // than one IME. 491 // than one IME.
382 this.showGlobeKey = inputMethods.length > 1; 492 this.showGlobeKey = inputMethods.length > 1;
383 this.readyState_.markStateReady(StateType.IME_LIST_READY); 493 this.readyState_.markStateReady(StateType.IME_LIST_READY);
384 this.maybeDispatchSettingsReadyEvent_(); 494 this.maybeDispatchSettingsReadyEvent_();
385 }).bind(this)); 495 }).bind(this));
386 inputview.getInputMethodConfig((function(config) { 496 inputview.getInputMethodConfig((function(config) {
387 this.isQPInputView = !!config['isNewQPInputViewEnabled']; 497 this.isQPInputView = !!config['isNewQPInputViewEnabled'];
498 var voiceEnabled = config['isVoiceInputEnabled'];
499 if (goog.isDef(voiceEnabled)) {
500 this.isVoiceInputEnabled = !!voiceEnabled;
501 }
502 i18n.input.chrome.inputview.GlobalFlags.isQPInputView =
503 this.isQPInputView;
388 this.readyState_.markStateReady(StateType.INPUT_METHOD_CONFIG_READY); 504 this.readyState_.markStateReady(StateType.INPUT_METHOD_CONFIG_READY);
389 this.maybeDispatchSettingsReadyEvent_(); 505 this.maybeDispatchSettingsReadyEvent_();
390 }).bind(this)); 506 }).bind(this));
391 } else { 507 } else {
392 this.readyState_.markStateReady(StateType.IME_LIST_READY); 508 this.readyState_.markStateReady(StateType.IME_LIST_READY);
393 this.readyState_.markStateReady(StateType.KEYBOARD_CONFIG_READY); 509 this.readyState_.markStateReady(StateType.KEYBOARD_CONFIG_READY);
394 this.readyState_.markStateReady(StateType.INPUT_METHOD_CONFIG_READY); 510 this.readyState_.markStateReady(StateType.INPUT_METHOD_CONFIG_READY);
395 } 511 }
396 512
397 this.maybeDispatchSettingsReadyEvent_(); 513 this.maybeDispatchSettingsReadyEvent_();
(...skipping 201 matching lines...) Expand 10 before | Expand all | Expand 10 after
599 case Type.CANDIDATES_BACK: 715 case Type.CANDIDATES_BACK:
600 this.onCandidatesBack_(msg); 716 this.onCandidatesBack_(msg);
601 break; 717 break;
602 case Type.CONTEXT_FOCUS: 718 case Type.CONTEXT_FOCUS:
603 this.onContextFocus_(msg); 719 this.onContextFocus_(msg);
604 break; 720 break;
605 case Type.CONTEXT_BLUR: 721 case Type.CONTEXT_BLUR:
606 this.onContextBlur_(); 722 this.onContextBlur_();
607 break; 723 break;
608 case Type.SURROUNDING_TEXT_CHANGED: 724 case Type.SURROUNDING_TEXT_CHANGED:
609 this.onSurroundingTextChanged_(request[Name.TEXT]); 725 this.onSurroundingTextChanged_(request[Name.TEXT],
726 request[Name.ANCHOR],
727 request[Name.FOCUS]);
610 break; 728 break;
611 case Type.UPDATE_SETTINGS: 729 case Type.UPDATE_SETTINGS:
612 this.onUpdateSettings_(msg); 730 this.onUpdateSettings_(msg);
613 break; 731 break;
614 case Type.VOICE_STATE_CHANGE: 732 case Type.VOICE_STATE_CHANGE:
615 case Type.HWT_NETWORK_ERROR: 733 case Type.HWT_NETWORK_ERROR:
616 case Type.FRONT_TOGGLE_LANGUAGE_STATE: 734 case Type.FRONT_TOGGLE_LANGUAGE_STATE:
617 this.dispatchEvent(new i18n.input.chrome.message.Event(type, msg)); 735 this.dispatchEvent(new i18n.input.chrome.message.Event(type, msg));
618 break; 736 break;
619 } 737 }
(...skipping 49 matching lines...) Expand 10 before | Expand all | Expand 10 after
669 * Unset the inputtool 787 * Unset the inputtool
670 */ 788 */
671 Adapter.prototype.unsetController = function() { 789 Adapter.prototype.unsetController = function() {
672 chrome.runtime.sendMessage( 790 chrome.runtime.sendMessage(
673 goog.object.create( 791 goog.object.create(
674 Name.TYPE, 792 Name.TYPE,
675 Type.UNSET_CONTROLLER)); 793 Type.UNSET_CONTROLLER));
676 }; 794 };
677 }); // goog.scope 795 }); // goog.scope
678 796
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698