Commit | Line | Data |
---|---|---|
6e512b93 ASL |
1 | /******************************************************************************* |
2 | * Copyright (c) 2009 Ericsson | |
3 | * | |
4 | * All rights reserved. This program and the accompanying materials are | |
5 | * made available under the terms of the Eclipse Public License v1.0 which | |
6 | * accompanies this distribution, and is available at | |
7 | * http://www.eclipse.org/legal/epl-v10.html | |
8 | * | |
9 | * Contributors: | |
10 | * Francois Chouinard - Initial API and implementation | |
11 | *******************************************************************************/ | |
12 | ||
13 | package org.eclipse.linuxtools.lttng.ui.views.histogram; | |
14 | ||
15 | import org.eclipse.swt.SWT; | |
16 | import org.eclipse.swt.events.FocusEvent; | |
17 | import org.eclipse.swt.events.FocusListener; | |
18 | import org.eclipse.swt.events.KeyEvent; | |
19 | import org.eclipse.swt.events.KeyListener; | |
20 | import org.eclipse.swt.graphics.Font; | |
21 | import org.eclipse.swt.graphics.FontData; | |
22 | import org.eclipse.swt.layout.GridData; | |
23 | import org.eclipse.swt.layout.GridLayout; | |
24 | import org.eclipse.swt.widgets.Composite; | |
25 | import org.eclipse.swt.widgets.Display; | |
26 | import org.eclipse.swt.widgets.Group; | |
27 | import org.eclipse.swt.widgets.Label; | |
28 | import org.eclipse.swt.widgets.Text; | |
29 | ||
544fe9b7 WB |
30 | /** |
31 | * <b><u>TimeTextGroup</u></b> | |
32 | * <p> | |
33 | * Special control for HistogramView | |
34 | * <p> | |
35 | * This control will give you a group, a text box and a label at once. | |
36 | */ | |
6e512b93 | 37 | public class TimeTextGroup implements FocusListener, KeyListener { |
3e9fdb8b FC |
38 | |
39 | /* | |
40 | // 2010-06-10 Yuriy: Has been moved to header into HistogramView.java | |
7c1540ab | 41 | protected static final String NANOSEC_LABEL = "sec"; |
3e9fdb8b | 42 | */ |
f05aabed FC |
43 | private static final String LONGEST_STRING_VALUE = "." + Long.MAX_VALUE; |
44 | private static final int MAX_CHAR_IN_TEXTBOX = LONGEST_STRING_VALUE.length(); | |
6e512b93 | 45 | |
82513d52 | 46 | // The "small font" height used to display time will be "default font" minus this constant |
f05aabed FC |
47 | private static final int VERY_SMALL_FONT_MODIFIER = 2; |
48 | private static final int SMALL_FONT_MODIFIER = 1; | |
82513d52 | 49 | |
7c1540ab | 50 | // Indentation size |
1a971e96 | 51 | // private static final int DEFAULT_INDENT_SIZE = 10; |
6e512b93 | 52 | |
f05aabed FC |
53 | private HistogramView parentView = null; |
54 | private AsyncTimeTextGroupRedrawer asyncRedrawer = null; | |
6e512b93 | 55 | |
f05aabed FC |
56 | private Group grpName = null; |
57 | private Text txtNanosec = null; | |
58 | private Label lblNanosec = null; | |
7c1540ab | 59 | |
f05aabed | 60 | private long timeValue = 0L; |
6e512b93 | 61 | |
544fe9b7 WB |
62 | /** |
63 | * Default Constructor.<p> | |
64 | * | |
65 | * @param newParentView Parent HistogramView | |
66 | * @param parent Parent Composite, used to position the inner controls. | |
67 | * @param textStyle Style of the textbox. Usually SWT.BORDER or SWT.NONE (or anything that suit a Text) | |
68 | * @param groupStyle Style of the group. Anything that suite a Text | |
69 | */ | |
6e512b93 | 70 | public TimeTextGroup(HistogramView newParentView, Composite parent, int textStyle, int groupStyle) { |
7c1540ab WB |
71 | this(newParentView, parent, textStyle, groupStyle, "", HistogramConstant.formatNanoSecondsTime(0L), false); |
72 | } | |
73 | ||
74 | /** | |
75 | * Default Constructor with adjustement for small screen.<p> | |
76 | * | |
77 | * @param newParentView Parent HistogramView | |
78 | * @param parent Parent Composite, used to position the inner controls. | |
79 | * @param textStyle Style of the textbox. Usually SWT.BORDER or SWT.NONE (or anything that suit a Text) | |
80 | * @param groupStyle Style of the group. Anything that suite a Text | |
81 | * @param isSpaceSaverNeeded Value that tell if we try to save some space in the control. | |
82 | */ | |
83 | public TimeTextGroup(HistogramView newParentView, Composite parent, int textStyle, int groupStyle, boolean isSpaceSaverNeeded) { | |
84 | this(newParentView, parent, textStyle, groupStyle, "", HistogramConstant.formatNanoSecondsTime(0L), isSpaceSaverNeeded); | |
6e512b93 ASL |
85 | } |
86 | ||
544fe9b7 WB |
87 | /** |
88 | * Default Constructor, allow you to give the groupname and the textbox value.<p> | |
89 | * | |
90 | * @param newParentView Parent HistogramView | |
91 | * @param parent Parent Composite, used to position the inner controls. | |
92 | * @param textStyle Style of the textbox. Usually SWT.BORDER or SWT.NONE (or anything that suit a Text) | |
93 | * @param groupStyle Style of the group. Anything that suite a Text | |
94 | * @param groupValue Value (label) of the group. | |
95 | * @param textValue Value of the textbox. | |
96 | */ | |
6e512b93 | 97 | public TimeTextGroup(HistogramView newParentView, Composite parent, int textStyle, int groupStyle, String groupValue, String textValue) { |
7c1540ab WB |
98 | this(newParentView, parent, textStyle, groupStyle, groupValue, textValue, false); |
99 | } | |
100 | ||
101 | /** | |
1a971e96 FC |
102 | * Default Constructor with adjustment for small screen, allow you to give the group name and the text box value.<p> |
103 | * | |
7c1540ab WB |
104 | * @param newParentView Parent HistogramView |
105 | * @param parent Parent Composite, used to position the inner controls. | |
1a971e96 | 106 | * @param textStyle Style of the text box. Usually SWT.BORDER or SWT.NONE (or anything that suit a Text) |
7c1540ab WB |
107 | * @param groupStyle Style of the group. Anything that suite a Text |
108 | * @param groupValue Value (label) of the group. | |
1a971e96 | 109 | * @param textValue Value of the text box. |
7c1540ab WB |
110 | * @param isSpaceSaverNeeded Value that tell if we try to save some space in the control. |
111 | */ | |
112 | public TimeTextGroup(HistogramView newParentView, Composite parent, int textStyle, int groupStyle, String groupValue, String textValue, boolean isSpaceSaverNeeded) { | |
6e512b93 ASL |
113 | Font font = parent.getFont(); |
114 | FontData tmpFontData = font.getFontData()[0]; | |
7c1540ab WB |
115 | |
116 | Font smallFont = null; | |
117 | int textBoxSize = -1; | |
1a971e96 | 118 | // int indentSize = -1; |
7c1540ab WB |
119 | |
120 | // If we were asked to save size, calculate the correct value here | |
121 | if ( isSpaceSaverNeeded == true ) { | |
122 | smallFont = new Font(font.getDevice(), tmpFontData.getName(), tmpFontData.getHeight()-VERY_SMALL_FONT_MODIFIER, tmpFontData.getStyle()); | |
123 | ||
124 | // No minimum textBoxSize and no indent size | |
125 | textBoxSize = 0; | |
1a971e96 | 126 | // indentSize = 0; |
7c1540ab WB |
127 | } |
128 | else { | |
129 | // We use only a slightly smaller font | |
130 | smallFont = new Font(font.getDevice(), tmpFontData.getName(), tmpFontData.getHeight()-SMALL_FONT_MODIFIER, tmpFontData.getStyle()); | |
131 | ||
132 | // ** Creation of the textbox | |
133 | // Calculate the optimal size of the textbox already | |
134 | // This will avoid the control to move around and resize when bigger value are given | |
135 | textBoxSize = HistogramConstant.getTextSizeInControl(parent, LONGEST_STRING_VALUE); | |
136 | ||
137 | // Default indent | |
1a971e96 | 138 | // indentSize = DEFAULT_INDENT_SIZE; |
7c1540ab WB |
139 | } |
140 | ||
6e512b93 ASL |
141 | parentView = newParentView; |
142 | ||
544fe9b7 | 143 | // ** Creation of the group |
1a971e96 FC |
144 | // GridLayout gridLayoutgroup = new GridLayout(2, false); |
145 | GridLayout gridLayoutgroup = new GridLayout(1, false); | |
82513d52 WB |
146 | gridLayoutgroup.horizontalSpacing = 0; |
147 | gridLayoutgroup.verticalSpacing = 0; | |
6e512b93 ASL |
148 | grpName = new Group(parent, groupStyle); |
149 | grpName.setText(groupValue); | |
150 | grpName.setFont(smallFont); | |
82513d52 | 151 | grpName.setLayout(gridLayoutgroup); |
6e512b93 ASL |
152 | |
153 | txtNanosec = new Text(grpName, textStyle); | |
154 | txtNanosec.setTextLimit( MAX_CHAR_IN_TEXTBOX ); | |
1a971e96 | 155 | txtNanosec.setText(textValue); |
82513d52 WB |
156 | txtNanosec.setFont(smallFont); |
157 | GridData gridDataTextBox = new GridData(SWT.LEFT, SWT.CENTER, true, false); | |
1a971e96 | 158 | gridDataTextBox.horizontalIndent = 0; // indentSize; |
82513d52 | 159 | gridDataTextBox.verticalIndent = 0; |
6e512b93 ASL |
160 | gridDataTextBox.minimumWidth = textBoxSize; |
161 | txtNanosec.setLayoutData(gridDataTextBox); | |
162 | ||
544fe9b7 | 163 | // ** Creation of the label |
3e9fdb8b | 164 | /* |
6e512b93 ASL |
165 | lblNanosec = new Label(grpName, SWT.LEFT); |
166 | lblNanosec.setText(NANOSEC_LABEL); | |
167 | lblNanosec.setFont(smallFont); | |
82513d52 | 168 | GridData gridDataLabel = new GridData(SWT.LEFT, SWT.CENTER, false, false); |
7c1540ab | 169 | gridDataLabel.horizontalIndent = indentSize; |
82513d52 WB |
170 | gridDataLabel.verticalIndent = 0; |
171 | lblNanosec.setLayoutData(gridDataLabel); | |
3e9fdb8b | 172 | */ |
6e512b93 | 173 | |
544fe9b7 | 174 | // Add all listener |
6e512b93 ASL |
175 | addNeededListeners(); |
176 | } | |
177 | ||
544fe9b7 WB |
178 | /* |
179 | * Create and add all listeners needed by our control.<p> | |
180 | */ | |
7c1540ab | 181 | protected void addNeededListeners() { |
6e512b93 ASL |
182 | |
183 | // AsyncCanvasRedrawer is an internal class | |
184 | // This is used to redraw the canvas without danger from a different thread | |
185 | asyncRedrawer = new AsyncTimeTextGroupRedrawer(this); | |
186 | ||
187 | txtNanosec.addFocusListener(this); | |
188 | txtNanosec.addKeyListener(this); | |
189 | } | |
190 | ||
544fe9b7 WB |
191 | /** |
192 | * Getter for the layout data currently in use.<p> | |
193 | * | |
194 | * @return the layout | |
195 | */ | |
196 | public Object getLayoutData() { | |
197 | return grpName.getLayoutData(); | |
198 | } | |
6e512b93 | 199 | |
544fe9b7 WB |
200 | /** |
201 | * Set a new layoutData for our control.<p> | |
202 | * | |
203 | * @param layoutData the new layout data | |
204 | */ | |
6e512b93 ASL |
205 | public void setLayoutData(Object layoutData) { |
206 | grpName.setLayoutData(layoutData); | |
207 | } | |
208 | ||
544fe9b7 WB |
209 | /** |
210 | * Get the control's parent.<p> | |
211 | * | |
212 | * @return Currently used parent | |
213 | */ | |
6e512b93 ASL |
214 | public Composite getParent() { |
215 | return grpName.getParent(); | |
216 | } | |
217 | ||
544fe9b7 WB |
218 | /** |
219 | * Set a new parent for the control.<p> | |
220 | * | |
221 | * @return Currently used parent | |
222 | */ | |
6e512b93 ASL |
223 | public void setParent(Composite newParent) { |
224 | grpName.setParent(newParent); | |
225 | txtNanosec.setParent(newParent); | |
226 | lblNanosec.setParent(newParent); | |
227 | } | |
228 | ||
c1c69938 FC |
229 | |
230 | public boolean isDisposed() { | |
231 | return grpName.isDisposed(); | |
232 | } | |
233 | ||
544fe9b7 WB |
234 | /** |
235 | * Getter for the time value of the control.<p> | |
236 | * | |
237 | * @return The nanoseconds time value | |
238 | */ | |
1406f802 | 239 | public long getValue() { |
6e512b93 ASL |
240 | return timeValue; |
241 | } | |
242 | ||
544fe9b7 WB |
243 | /** |
244 | * Set a new String value to the control.<p> | |
1406f802 | 245 | * Note : The String value will be converted in long before being applied; |
544fe9b7 WB |
246 | * if any conversion error occur, 0 will be used. <p> |
247 | * | |
248 | * @param newTimeAsString The value to convert and set. | |
249 | */ | |
250 | public void setValue(String newTimeAsString) { | |
1406f802 | 251 | long timeAsLong = HistogramConstant.convertStringToNanoseconds(newTimeAsString); |
544fe9b7 WB |
252 | setValue( timeAsLong ); |
253 | } | |
6e512b93 | 254 | |
544fe9b7 WB |
255 | /** |
256 | * Set a new value to the control.<p> | |
257 | * Note : The value will be formatted as nanosecond value, | |
258 | * missing zero will be added if needed.<p> | |
259 | * | |
260 | * @param newTime The value to set. | |
261 | */ | |
1406f802 | 262 | public void setValue(long newTime) { |
544fe9b7 WB |
263 | timeValue = newTime; |
264 | txtNanosec.setText( HistogramConstant.formatNanoSecondsTime(newTime) ); | |
265 | } | |
266 | ||
267 | /** | |
268 | * Set a new String value, asynchronously.<p> | |
269 | * This will call setValue(String) in async.Exec to avoid Thread Access problem to UI.<p> | |
270 | * | |
271 | * @param newTimeAsString The value to convert and set. | |
272 | */ | |
6e512b93 | 273 | public void setValueAsynchronously(String newTimeAsString) { |
1406f802 | 274 | long timeAsLong = HistogramConstant.convertStringToNanoseconds(newTimeAsString); |
6e512b93 ASL |
275 | setValueAsynchronously( timeAsLong ); |
276 | } | |
277 | ||
544fe9b7 WB |
278 | /** |
279 | * Set a new String value, asynchronously.<p> | |
1406f802 | 280 | * This will call setValue(long) in async.Exec to avoid Thread Access problem to UI.<p> |
544fe9b7 WB |
281 | * |
282 | * @param newTimeAsString The value to set. | |
283 | */ | |
1406f802 | 284 | public void setValueAsynchronously(long newTime) { |
6e512b93 ASL |
285 | // Set the correct value ASAP |
286 | timeValue = newTime; | |
287 | ||
288 | // Create a new redrawer in case it doesn't exist yet (we never know with thread!) | |
289 | if ( asyncRedrawer == null ) { | |
290 | asyncRedrawer = new AsyncTimeTextGroupRedrawer(this); | |
291 | } | |
292 | ||
293 | asyncRedrawer.asynchronousSetValue(newTime); | |
294 | } | |
295 | ||
544fe9b7 WB |
296 | /** |
297 | * Set a new group name (label) for this control.<p> | |
298 | * | |
299 | * @param newName The new name to set. | |
300 | */ | |
301 | public void setGroupName(String newName) { | |
302 | grpName.setText(newName); | |
6e512b93 ASL |
303 | } |
304 | ||
544fe9b7 WB |
305 | /** |
306 | * Set a new group name (label) for this control, asynchronously.<p> | |
1406f802 | 307 | * This will call setValue(long) in async.Exec to avoid Thread Access problem to UI.<p> |
544fe9b7 WB |
308 | * |
309 | * @param newName The new name to set. | |
310 | */ | |
311 | public void setGroupNameAsynchronously(String newGroupName) { | |
312 | // Create a new redrawer in case it doesn't exist yet (we never know with thread!) | |
313 | if ( asyncRedrawer == null ) { | |
314 | asyncRedrawer = new AsyncTimeTextGroupRedrawer(this); | |
315 | } | |
316 | ||
317 | asyncRedrawer.asynchronousSetGroupName(newGroupName); | |
088c1d4e | 318 | } |
6e512b93 | 319 | |
544fe9b7 WB |
320 | |
321 | /** | |
322 | * Method to call the "Asynchronous redrawer" for this time text group<p> | |
323 | * This allow safe redraw from different threads. | |
324 | */ | |
325 | public void redrawAsynchronously() { | |
326 | // Create a new redrawer in case it doesn't exist yet (we never know with thread!) | |
327 | if ( asyncRedrawer == null ) { | |
328 | asyncRedrawer = new AsyncTimeTextGroupRedrawer(this); | |
329 | } | |
330 | ||
331 | asyncRedrawer.asynchronousRedraw(); | |
332 | } | |
333 | ||
334 | /** | |
335 | * Redraw the control | |
336 | */ | |
337 | public void redraw () { | |
338 | grpName.redraw(); | |
339 | txtNanosec.redraw(); | |
340 | lblNanosec.redraw(); | |
341 | } | |
342 | ||
343 | /* | |
344 | * This function is called when an user enter a new string in the control by hand.<p> | |
345 | * It will ensure the format of the String is valid. | |
346 | */ | |
7c1540ab | 347 | protected void handleNewStringValue() { |
6e512b93 | 348 | String valueInText = txtNanosec.getText(); |
1406f802 | 349 | long valueAsLong = HistogramConstant.convertStringToNanoseconds(valueInText); |
6e512b93 ASL |
350 | |
351 | if ( getValue() != valueAsLong ) { | |
352 | setValue(valueAsLong); | |
544fe9b7 | 353 | // Notify our parent that the control was updated |
6e512b93 ASL |
354 | notifyParentUpdatedTextGroupValue(); |
355 | } | |
356 | } | |
357 | ||
544fe9b7 WB |
358 | /** |
359 | * This function notify our parent HistogramView that our value changed. | |
360 | */ | |
6e512b93 ASL |
361 | public void notifyParentUpdatedTextGroupValue() { |
362 | parentView.timeTextGroupChangeNotification(); | |
363 | } | |
364 | ||
544fe9b7 WB |
365 | /** |
366 | * Function that is called when the canvas get focus.<p> | |
367 | * | |
368 | * Doesn't do anything yet... | |
369 | * | |
370 | * @param event The focus event generated. | |
371 | */ | |
d4011df2 | 372 | @Override |
6e512b93 ASL |
373 | public void focusGained(FocusEvent event) { |
374 | // Nothing to do yet | |
375 | } | |
376 | ||
544fe9b7 WB |
377 | /** |
378 | * Function that is called when the canvas loose focus.<p> | |
379 | * It will validate that the String entered by the user (if any) is valid.<p> | |
380 | * | |
381 | * @param event The focus event generated. | |
382 | */ | |
d4011df2 | 383 | @Override |
6e512b93 ASL |
384 | public void focusLost(FocusEvent event) { |
385 | handleNewStringValue(); | |
386 | } | |
387 | ||
544fe9b7 WB |
388 | /** |
389 | * Function that is called when a key is pressed.<p> | |
390 | * Possible actions : | |
391 | * - Enter (CR) : Validate the entered String.<p> | |
392 | * | |
393 | * @param event The KeyEvent generated when the key was pressed. | |
394 | */ | |
d4011df2 | 395 | @Override |
6e512b93 ASL |
396 | public void keyPressed(KeyEvent event) { |
397 | switch (event.keyCode) { | |
398 | // SWT.CR is "ENTER" Key | |
399 | case SWT.CR: | |
400 | handleNewStringValue(); | |
401 | break; | |
402 | default: | |
403 | break; | |
404 | } | |
405 | } | |
406 | ||
407 | /** | |
544fe9b7 WB |
408 | * Function that is called when a key is released.<p> |
409 | * Possible actions : | |
410 | * Nothing yet | |
6e512b93 | 411 | * |
544fe9b7 | 412 | * @param event The KeyEvent generated when the key was pressed. |
6e512b93 | 413 | */ |
d4011df2 | 414 | @Override |
544fe9b7 | 415 | public void keyReleased(KeyEvent e) { |
6e512b93 ASL |
416 | |
417 | } | |
418 | } | |
419 | ||
420 | /** | |
421 | * <b><u>AsyncTimeTextGroupRedrawer Inner Class</u></b> | |
422 | * <p> | |
423 | * Asynchronous redrawer for the TimeTextGroup | |
424 | * <p> | |
425 | * This class role is to call method that update the UI on asynchronously. | |
426 | * This should prevent any "invalid thread access" exception when trying to update UI from a different thread. | |
427 | */ | |
428 | class AsyncTimeTextGroupRedrawer { | |
429 | ||
430 | private TimeTextGroup parentTimeTextGroup = null; | |
431 | ||
432 | /** | |
433 | * AsyncTimeTextGroupRedrawer constructor. | |
434 | * | |
435 | * @param newParent Related time text group. | |
436 | */ | |
437 | public AsyncTimeTextGroupRedrawer(TimeTextGroup newParent) { | |
438 | parentTimeTextGroup = newParent; | |
439 | } | |
440 | ||
441 | /** | |
544fe9b7 | 442 | * Asynchronous SetValue for time text group. |
6e512b93 ASL |
443 | * |
444 | * Basically, it just run "getParent().setValue(time)" in asyncExec. | |
445 | * | |
446 | * @param newTime The new time to set | |
447 | */ | |
1a971e96 | 448 | public void asynchronousSetValue(final long newTime) { |
db1ea19b | 449 | // Ignore setting of value if widget is disposed |
c1c69938 | 450 | if (parentTimeTextGroup.isDisposed()) return; |
db1ea19b | 451 | |
6e512b93 ASL |
452 | Display display = parentTimeTextGroup.getParent().getDisplay(); |
453 | display.asyncExec(new Runnable() { | |
d4011df2 | 454 | @Override |
c1c69938 FC |
455 | public void run() { |
456 | if (!parentTimeTextGroup.isDisposed()) { | |
1a971e96 | 457 | parentTimeTextGroup.setValue(newTime); |
db1ea19b | 458 | } |
6e512b93 ASL |
459 | } |
460 | }); | |
461 | } | |
462 | ||
544fe9b7 WB |
463 | /** |
464 | * Asynchronous SetGroupName for time text group. | |
465 | * | |
466 | * Basically, it just run "getParent().setGroupName(name)" in asyncExec. | |
467 | * | |
468 | * @param newGroupName The new group name to set | |
469 | */ | |
470 | public void asynchronousSetGroupName(String newGroupName) { | |
db1ea19b | 471 | // Ignore setting of name if widget is disposed |
c1c69938 | 472 | if (parentTimeTextGroup.isDisposed()) return; |
db1ea19b | 473 | |
544fe9b7 | 474 | final String tmpName = newGroupName; |
544fe9b7 WB |
475 | Display display = parentTimeTextGroup.getParent().getDisplay(); |
476 | display.asyncExec(new Runnable() { | |
d4011df2 | 477 | @Override |
c1c69938 FC |
478 | public void run() { |
479 | if (!parentTimeTextGroup.isDisposed()) { | |
db1ea19b FC |
480 | parentTimeTextGroup.setGroupName(tmpName); |
481 | } | |
544fe9b7 WB |
482 | } |
483 | }); | |
484 | } | |
485 | ||
6e512b93 ASL |
486 | /** |
487 | * Function to redraw the related time text group asynchonously.<p> | |
488 | * | |
489 | * Basically, it just run "getParent().redraw()" in asyncExec. | |
490 | * | |
491 | */ | |
492 | public void asynchronousRedraw() { | |
db1ea19b | 493 | // Ignore redraw if widget is disposed |
c1c69938 | 494 | if (parentTimeTextGroup.isDisposed()) return; |
db1ea19b | 495 | |
6e512b93 ASL |
496 | Display display = parentTimeTextGroup.getParent().getDisplay(); |
497 | display.asyncExec(new Runnable() { | |
d4011df2 | 498 | @Override |
c1c69938 FC |
499 | public void run() { |
500 | if (!parentTimeTextGroup.isDisposed()) { | |
db1ea19b FC |
501 | parentTimeTextGroup.getParent().redraw(); |
502 | } | |
6e512b93 ASL |
503 | } |
504 | }); | |
505 | } | |
506 | } |