1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23 package org.diyefi.openlogviewer.optionpanel;
24
25 import java.awt.Color;
26 import java.awt.Component;
27 import java.awt.Dimension;
28 import java.awt.Point;
29 import java.awt.event.ActionEvent;
30 import java.awt.event.ActionListener;
31 import java.awt.event.ContainerEvent;
32 import java.awt.event.ContainerListener;
33 import java.awt.event.MouseEvent;
34 import java.awt.event.MouseListener;
35 import java.awt.event.MouseMotionAdapter;
36
37 import java.util.ArrayList;
38 import java.util.Iterator;
39 import java.util.List;
40
41 import javax.swing.JFrame;
42 import javax.swing.JPanel;
43 import javax.swing.JButton;
44 import javax.swing.JLayeredPane;
45 import javax.swing.JScrollPane;
46 import javax.swing.JLabel;
47 import javax.swing.JInternalFrame;
48 import javax.swing.JTextField;
49 import javax.swing.JColorChooser;
50 import javax.swing.BorderFactory;
51
52 import org.diyefi.openlogviewer.OpenLogViewer;
53 import org.diyefi.openlogviewer.genericlog.GenericDataElement;
54 import org.diyefi.openlogviewer.genericlog.GenericLog;
55 import org.diyefi.openlogviewer.propertypanel.SingleProperty;
56
57 public class OptionFrameV2 extends JFrame {
58 private static final long serialVersionUID = 1L;
59
60 private static final int NUMBER_OF_COLS_OF_FREEEMS_FIELDS = 8;
61 private static final int HEIGHT_IN_FIELDS = 12;
62 private static final int NUMBER_OF_ADD_BUTTONS = 1;
63
64 private static final int WIDTH_OF_BOXES = NUMBER_OF_COLS_OF_FREEEMS_FIELDS;
65 private static final int HEIGHT_OF_BOXES = 2;
66 private static final int MAX_NUMBER_OF_BOXES = WIDTH_OF_BOXES * HEIGHT_OF_BOXES;
67
68
69 private static final int WTF = 4;
70 private static final int WTF2 = 3;
71
72 private static final int COMP_HEIGHT = 20;
73 private static final int COMP_WIDTH = 200;
74 private static final int PANEL_WIDTH = 140;
75 private static final int PANEL_HEIGHT = 120;
76 private static final int SCROLL_BAR_SIZE = 16;
77
78
79 private static final int WIDTH_OF_WINDOW = (NUMBER_OF_COLS_OF_FREEEMS_FIELDS * PANEL_WIDTH);
80 private static final int HEIGHT_OF_WINDOW = ((PANEL_HEIGHT * HEIGHT_OF_BOXES) + (COMP_HEIGHT * (HEIGHT_IN_FIELDS + NUMBER_OF_ADD_BUTTONS)));
81
82 private JFrame thisRef;
83 private JPanel inactiveHeaders;
84 private ModifyGraphPane infoPanel;
85 private JButton addDivisionButton;
86
87 private JLayeredPane layeredPane;
88 private List<JPanel> activePanelList;
89
90 public OptionFrameV2() {
91 super("Graphing Option Pane");
92 this.setSize(WIDTH_OF_WINDOW + WTF2, HEIGHT_OF_WINDOW + COMP_HEIGHT + SCROLL_BAR_SIZE + WTF);
93 this.setPreferredSize(this.getSize());
94
95 this.setDefaultCloseOperation(JFrame.HIDE_ON_CLOSE);
96 thisRef = this;
97 activePanelList = new ArrayList<JPanel>();
98 layeredPane = new JLayeredPane();
99 layeredPane.setPreferredSize(new Dimension(WIDTH_OF_WINDOW, HEIGHT_OF_WINDOW));
100
101 final JScrollPane scroll = new JScrollPane(layeredPane);
102
103 inactiveHeaders = initHeaderPanel();
104 layeredPane.add(inactiveHeaders);
105 infoPanel = new ModifyGraphPane();
106 this.add(infoPanel);
107
108 this.add(scroll);
109 addActiveHeaderPanel();
110 }
111
112 private JPanel initHeaderPanel() {
113 final JPanel ih = new JPanel();
114 ih.setLayout(null);
115 ih.setName("Drop InactiveHeaderPanel");
116 this.addDivisionButton = new JButton("Add Division");
117 addDivisionButton.setBounds(0, 0, PANEL_WIDTH, COMP_HEIGHT);
118 addDivisionButton.addActionListener(addDivisionListener);
119 ih.add(addDivisionButton);
120 ih.setBounds(0, 0, 1280, (COMP_HEIGHT * (HEIGHT_IN_FIELDS + NUMBER_OF_ADD_BUTTONS)));
121 return ih;
122 }
123
124 private ActionListener addDivisionListener = new ActionListener() {
125 @Override
126 public void actionPerformed(final ActionEvent e) {
127 addActiveHeaderPanel();
128 }
129 };
130
131 private ActionListener remDivisionListener = new ActionListener() {
132 @Override
133 public void actionPerformed(final ActionEvent e) {
134 remActiveHeaderPanel(e);
135 }
136 };
137
138 private ContainerListener addRemoveListener = new ContainerListener() {
139 @Override
140 public void componentAdded(final ContainerEvent e) {
141
142 if (e.getChild() != null) {
143 if (e.getChild() instanceof ActiveHeaderLabel) {
144 ((ActiveHeaderLabel) e.getChild()).setEnabled(true);
145 ((ActiveHeaderLabel) e.getChild()).setSelected(true);
146 ((ActiveHeaderLabel) e.getChild()).getGDE().setSplitNumber(activePanelList.indexOf(e.getChild().getParent()) + 1);
147 }
148 }
149 }
150
151 @Override
152 public void componentRemoved(final ContainerEvent e) {
153 if (e.getChild() != null) {
154 if (e.getChild() instanceof ActiveHeaderLabel) {
155 ((ActiveHeaderLabel) e.getChild()).setEnabled(false);
156 ((ActiveHeaderLabel) e.getChild()).setSelected(false);
157 }
158 for (int i = 0; i < e.getContainer().getComponentCount(); i++) {
159 if (e.getContainer().getComponent(i) instanceof ActiveHeaderLabel) {
160 e.getContainer().getComponent(i).setLocation(0, i * COMP_HEIGHT);
161 }
162 }
163 }
164 }
165 };
166
167 private void addActiveHeaderPanel() {
168 if (activePanelList.size() < MAX_NUMBER_OF_BOXES) {
169 final int row = activePanelList.size() / WIDTH_OF_BOXES;
170 final int col = activePanelList.size() % WIDTH_OF_BOXES;
171 final JPanel activePanel = new JPanel();
172 activePanelList.add(activePanel);
173 if (OpenLogViewer.getInstance() != null) {
174 OpenLogViewer.getInstance().getMultiGraphLayeredPane().setTotalSplits(activePanelList.size());
175 }
176 activePanel.setLayout(null);
177 activePanel.setName("Drop ActivePanel " + (activePanelList.indexOf(activePanel) + 1));
178 activePanel.addContainerListener(addRemoveListener);
179
180 activePanel.setBounds((col * PANEL_WIDTH), inactiveHeaders.getHeight() + PANEL_HEIGHT * row, PANEL_WIDTH, PANEL_HEIGHT);
181 activePanel.setBackground(Color.DARK_GRAY);
182 final JButton removeButton = new JButton("Remove");
183 removeButton.setToolTipText("Click Here to remove this division and associated Graphs");
184 removeButton.setBounds(0, 0, PANEL_WIDTH, COMP_HEIGHT);
185 removeButton.addActionListener(remDivisionListener);
186 activePanel.add(removeButton);
187 layeredPane.add(activePanel);
188
189 if (activePanelList.size() == MAX_NUMBER_OF_BOXES) {
190 addDivisionButton.setEnabled(false);
191 }
192 }
193 }
194
195 private void remActiveHeaderPanel(final ActionEvent e) {
196 final JPanel panel = (JPanel) ((JButton) e.getSource()).getParent();
197 activePanelList.remove(panel);
198 OpenLogViewer.getInstance().getMultiGraphLayeredPane().setTotalSplits(activePanelList.size());
199
200 for (int i = 0; i < panel.getComponentCount();) {
201 if (panel.getComponent(i) instanceof ActiveHeaderLabel) {
202 final ActiveHeaderLabel GCB = (ActiveHeaderLabel) panel.getComponent(i);
203 GCB.getInactivePanel().add(GCB);
204 GCB.setLocation(GCB.getInactiveLocation());
205 GCB.setSelected(false);
206 } else if (panel.getComponent(i) instanceof JButton) {
207 panel.remove(panel.getComponent(i));
208 } else {
209 i++;
210 }
211 }
212
213 panel.getParent().remove(panel);
214 for (int i = 0; i < activePanelList.size(); i++) {
215 final int row = i / WIDTH_OF_BOXES;
216 final int col = i % WIDTH_OF_BOXES;
217 activePanelList.get(i).setLocation((col * PANEL_WIDTH), inactiveHeaders.getHeight() + PANEL_HEIGHT * row);
218 }
219
220 if (!addDivisionButton.isEnabled()) {
221 addDivisionButton.setEnabled(true);
222 }
223
224
225 if (activePanelList.size() > 1) {
226 for (int i = 0; i < activePanelList.size(); i++) {
227 final JPanel active = activePanelList.get(i);
228 if (active.getComponentCount() > 1) {
229 for (int j = 0; j < active.getComponentCount(); j++) {
230 if (active.getComponent(j) instanceof ActiveHeaderLabel) {
231 ((ActiveHeaderLabel) active.getComponent(j)).getGDE().setSplitNumber(i + 1);
232 }
233 }
234 }
235 }
236 }
237
238 if (activePanelList.isEmpty()) {
239 addActiveHeaderPanel();
240 }
241
242 this.repaint();
243 }
244
245 private MouseMotionAdapter labelAdapter = new MouseMotionAdapter() {
246
247 @Override
248 public void mouseDragged(final MouseEvent e) {
249 final Component c = e.getComponent();
250 final ActiveHeaderLabel GCB = (ActiveHeaderLabel) c;
251 GCB.setDragging(true);
252 final Point pointNow = layeredPane.getMousePosition();
253 final Component parent = c.getParent();
254 if ((e.getModifiers() == 16) && (parent != null) && (pointNow != null)) {
255 if (!parent.contains(pointNow.x - parent.getX(), pointNow.y - parent.getY())) {
256 final Component parentsParent = parent.getParent();
257 final Component cn = parentsParent.getComponentAt(pointNow);
258 if (cn instanceof JPanel) {
259 final JPanel j = (JPanel) cn;
260 if (j.getName().contains("Drop")) {
261 j.add(c);
262
263 c.setLocation(
264
265 pointNow.x - parent.getX() - (c.getWidth() / 2),
266 pointNow.y - parent.getY() - (c.getHeight() / 2));
267 }
268 }
269 } else {
270 c.setLocation(c.getX() + e.getX() - (c.getWidth() / 2), c.getY() + e.getY() - (c.getHeight() / 2));
271 }
272 thisRef.repaint();
273 }
274 }
275 };
276
277 private boolean place(final ActiveHeaderLabel GCB) {
278 final int x = 0;
279 int y = COMP_HEIGHT;
280 while (y < GCB.getParent().getHeight()) {
281 if (GCB.getParent().getComponentAt(x, y) == GCB.getParent() || GCB.getParent().getComponentAt(x, y) == GCB) {
282 GCB.setLocation(x, y);
283 return true;
284 }
285 y = y + COMP_HEIGHT;
286 }
287 return false;
288 }
289
290 public void updateFromLog(final GenericLog datalog) {
291
292 while (activePanelList.size() > 0) {
293 activePanelList.get(0).removeAll();
294 layeredPane.remove(activePanelList.get(0));
295 activePanelList.remove(activePanelList.get(0));
296 }
297
298 addDivisionButton.setEnabled(true);
299
300 if (inactiveHeaders.getComponentCount() > 1) {
301 inactiveHeaders.removeAll();
302 inactiveHeaders.add(this.addDivisionButton);
303 }
304 this.addActiveHeaderPanel();
305
306 final ArrayList<ActiveHeaderLabel> tmpList = new ArrayList<ActiveHeaderLabel>();
307 final Iterator<String> headers = datalog.keySet().iterator();
308 String header = "";
309 ActiveHeaderLabel toBeAdded = null;
310
311 while (headers.hasNext()) {
312 header = headers.next();
313 final GenericDataElement GDE = datalog.get(header);
314 toBeAdded = new ActiveHeaderLabel();
315
316 toBeAdded.setName(header);
317 toBeAdded.setText(header);
318 toBeAdded.setRef(GDE);
319 toBeAdded.setEnabled(false);
320 toBeAdded.addMouseMotionListener(labelAdapter);
321 if (checkForProperties(GDE)) {
322 toBeAdded.setBackground(GDE.getDisplayColor());
323 }
324 tmpList.add(toBeAdded);
325 }
326
327 int j = 0;
328 int leftSide = 0;
329 for (int it = 0; it < tmpList.size(); it++) {
330 if (COMP_HEIGHT + (COMP_HEIGHT * (j + 1)) > inactiveHeaders.getHeight()) {
331 j = 0;
332 leftSide += PANEL_WIDTH;
333 }
334
335 tmpList.get(it).setBounds(leftSide, (COMP_HEIGHT + (COMP_HEIGHT * j)), PANEL_WIDTH, COMP_HEIGHT);
336 inactiveHeaders.add(tmpList.get(it));
337 j++;
338 }
339
340 this.repaint();
341 this.setDefaultCloseOperation(JFrame.ICONIFIED);
342 this.setVisible(true);
343 }
344
345 private boolean checkForProperties(GenericDataElement GDE) {
346 for (int i = 0; i < OpenLogViewer.getInstance().getProperties().size(); i++) {
347 if (OpenLogViewer.getInstance().getProperties().get(i).getHeader().equals(GDE.getName())) {
348 GDE.setDisplayColor(OpenLogViewer.getInstance().getProperties().get(i).getColor());
349 GDE.setDisplayMaxValue(OpenLogViewer.getInstance().getProperties().get(i).getMax());
350 GDE.setDisplayMinValue(OpenLogViewer.getInstance().getProperties().get(i).getMin());
351 GDE.setSplitNumber(OpenLogViewer.getInstance().getProperties().get(i).getSplit());
352
353 if (OpenLogViewer.getInstance().getProperties().get(i).isActive()) {
354 return true;
355 }
356 }
357 }
358 return false;
359 }
360
361 private class ModifyGraphPane extends JInternalFrame {
362 private static final long serialVersionUID = 1L;
363
364 private GenericDataElement GDE;
365 private ActiveHeaderLabel AHL;
366 private JLabel minLabel;
367 private JLabel maxLabel;
368 private JTextField minField;
369 private JTextField maxField;
370 private JButton resetButton;
371 private JButton applyButton;
372 private JButton saveButton;
373 private JButton colorButton;
374
375 private ActionListener resetButtonListener = new ActionListener() {
376 @Override
377 public void actionPerformed(final ActionEvent e) {
378 if (GDE != null) {
379 GDE.reset();
380 minField.setText(Double.toString(GDE.getDisplayMinValue()));
381 maxField.setText(Double.toString(GDE.getDisplayMaxValue()));
382 }
383 }
384 };
385
386 private ActionListener applyButtonListener = new ActionListener() {
387 @Override
388 public void actionPerformed(final ActionEvent e) {
389 if (GDE != null) {
390 changeGDEValues();
391 }
392 }
393 };
394
395 private ActionListener saveButtonListener = new ActionListener() {
396
397 @Override
398 public void actionPerformed(final ActionEvent e) {
399 if (GDE != null) {
400 changeGDEValues();
401 OpenLogViewer.getInstance().getPropertyPane().addPropertyAndSave(new SingleProperty(GDE));
402 }
403 }
404 };
405
406 private ActionListener colorButtonListener = new ActionListener() {
407
408 @Override
409 public void actionPerformed(final ActionEvent e) {
410 final Color c = JColorChooser.showDialog(
411 new JFrame(),
412 "Choose Background Color",
413 colorButton.getForeground());
414 if (c != null) {
415 colorButton.setForeground(c);
416 }
417 }
418 };
419
420 public ModifyGraphPane() {
421 this.setName("InfoPanel");
422 minLabel = new JLabel("Display Min:");
423 maxLabel = new JLabel("Display Max:");
424 minField = new JTextField(10);
425 maxField = new JTextField(10);
426 resetButton = new JButton("Reset Min/Max");
427 resetButton.addActionListener(resetButtonListener);
428
429 applyButton = new JButton("Apply");
430 applyButton.addActionListener(applyButtonListener);
431
432 saveButton = new JButton("Save");
433 saveButton.addActionListener(saveButtonListener);
434
435 colorButton = new JButton("Color");
436 colorButton.addActionListener(colorButtonListener);
437
438 minLabel.setBounds(0, 0, COMP_WIDTH / 2, COMP_HEIGHT);
439 minField.setBounds(100, 0, COMP_WIDTH / 2, COMP_HEIGHT);
440 maxLabel.setBounds(0, 20, COMP_WIDTH / 2, COMP_HEIGHT);
441 maxField.setBounds(100, 20, COMP_WIDTH / 2, COMP_HEIGHT);
442 colorButton.setBounds(0, 40, COMP_WIDTH, COMP_HEIGHT);
443 applyButton.setBounds(0, 60, COMP_WIDTH / 2, COMP_HEIGHT);
444 saveButton.setBounds(100, 60, COMP_WIDTH / 2, COMP_HEIGHT);
445 resetButton.setBounds(0, 80, COMP_WIDTH, COMP_HEIGHT);
446
447 this.setLayout(null);
448
449 this.add(minLabel);
450 this.add(minField);
451 this.add(maxLabel);
452 this.add(maxField);
453 this.add(colorButton);
454 this.add(applyButton);
455 this.add(saveButton);
456 this.add(resetButton);
457 this.setBounds(500, 180, 210, 133);
458 this.setMaximizable(false);
459 this.setDefaultCloseOperation(JInternalFrame.HIDE_ON_CLOSE);
460 this.setClosable(true);
461 }
462
463 public void setGDE(GenericDataElement gde, ActiveHeaderLabel ahl) {
464 this.GDE = gde;
465 this.AHL = ahl;
466 this.setTitle(GDE.getName());
467 minField.setText(String.valueOf(GDE.getDisplayMinValue()));
468 maxField.setText(String.valueOf(GDE.getDisplayMaxValue()));
469 colorButton.setForeground(GDE.getDisplayColor());
470 }
471
472 private void changeGDEValues() {
473 try {
474 GDE.setDisplayMaxValue(Double.parseDouble(maxField.getText()));
475 } catch (Exception ex) {
476 throw new RuntimeException("TODO: do something with Auto field");
477 }
478
479 try {
480 GDE.setDisplayMinValue(Double.parseDouble(minField.getText()));
481 } catch (Exception ex) {
482 throw new RuntimeException("TODO: do something with Auto field");
483 }
484
485 if (!GDE.getDisplayColor().equals(colorButton.getForeground())) {
486 GDE.setDisplayColor(colorButton.getForeground());
487 AHL.setForeground(colorButton.getForeground());
488 }
489 }
490 }
491
492 private class ActiveHeaderLabel extends JLabel implements Comparable<Object> {
493 private static final long serialVersionUID = 1L;
494
495 private GenericDataElement GDE;
496 private Point inactiveLocation;
497 private JPanel previousPanel;
498 private JPanel inactivePanel;
499 private boolean dragging;
500 private boolean selected;
501 private MouseListener selectedListener = new MouseListener() {
502
503 @Override
504 public void mouseClicked(final MouseEvent e) {
505 if (e.getModifiers() == 16) {
506 setSelected(!selected);
507 } else if (e.getModifiers() == 18) {
508 infoPanel.setGDE(GDE, (ActiveHeaderLabel) e.getSource());
509 if (!infoPanel.isVisible()) {
510 infoPanel.setVisible(true);
511 }
512 }
513 }
514
515 @Override
516 public void mouseEntered(final MouseEvent e) {
517 }
518
519 @Override
520 public void mouseExited(final MouseEvent e) {
521 }
522
523 @Override
524 public void mousePressed(final MouseEvent e) {
525 ActiveHeaderLabel GCB = (ActiveHeaderLabel) e.getSource();
526 GCB.setPreviousPanel((JPanel) GCB.getParent());
527 }
528
529 @Override
530 public void mouseReleased(final MouseEvent e) {
531 ActiveHeaderLabel GCB = (ActiveHeaderLabel) e.getSource();
532 if (GCB.isDragging()) {
533 if (GCB.getParent() == inactiveHeaders) {
534 GCB.setLocation(GCB.getInactiveLocation());
535 GCB.setSelected(false);
536 GCB.setEnabled(false);
537 } else {
538 if (!place(GCB)) {
539 if (GCB.getPreviousPanel() != GCB.getParent()) {
540 GCB.getPreviousPanel().add(GCB);
541 place(GCB);
542 }
543 if (GCB.getPreviousPanel() == GCB.getInactivePanel()) {
544 GCB.setLocation(GCB.getInactiveLocation());
545 GCB.setEnabled(false);
546 GCB.setSelected(false);
547 } else {
548 place(GCB);
549 }
550 thisRef.repaint();
551 }
552 }
553 GCB.setDragging(false);
554 }
555 }
556 };
557
558 public ActiveHeaderLabel() {
559 super();
560 addMouseListener(selectedListener);
561 super.setOpaque(false);
562 inactivePanel = inactiveHeaders;
563 dragging = false;
564 selected = false;
565 super.setBorder(BorderFactory.createEtchedBorder(Color.lightGray, Color.white));
566 }
567
568 @Override
569 public void setBounds(int x, int y, int width, int height) {
570 super.setBounds(x, y, width, height);
571 if (inactiveLocation == null) {
572 inactiveLocation = new Point(x, y);
573 }
574 }
575
576 public void setRef(final GenericDataElement GDE) {
577 this.GDE = GDE;
578
579
580 this.setToolTipTextPreliminary();
581
582 }
583
584 @Override
585 public String getToolTipText(final MouseEvent e) {
586 this.setToolTipTextFinal();
587 return getToolTipText();
588 }
589
590 public void setToolTipTextFinal() {
591 this.setToolTipText("<HTML> Data Stream: </b>" + GDE.getName()
592 + "</b><br>Min Value: <b>" + GDE.getMinValue()
593 + "</b><br>Min Display: <b>" + GDE.getDisplayMinValue()
594 + "</b><br>Max Value: <b>" + GDE.getMaxValue()
595 + "</b><br>Max Display: <b>" + GDE.getDisplayMaxValue()
596 + "<br>To modify Min and Max values for scaling purposes Ctrl+LeftClick</HTML>");
597 }
598
599 public void setToolTipTextPreliminary() {
600 this.setToolTipText("<HTML> Data Stream: </b>" + GDE.getName()
601 + "</b><br>Min Value: <b> -.-"
602 + "</b><br>Max Value: <b> -.-"
603 + "</b><br>Min Display: <b> -.-"
604 + "</b><br>Max Display: <b> -.-"
605 + "<br>To modify Min and Max values for scaling purposes Ctrl+LeftClick</HTML>");
606 }
607
608 public GenericDataElement getGDE() {
609 return GDE;
610 }
611
612 public JPanel getPreviousPanel() {
613 return previousPanel;
614 }
615
616 public void setPreviousPanel(JPanel previousPanel) {
617 this.previousPanel = previousPanel;
618 }
619
620 public Point getInactiveLocation() {
621 return inactiveLocation;
622 }
623
624 public JPanel getInactivePanel() {
625 return inactivePanel;
626 }
627
628 public boolean isDragging() {
629 return dragging;
630 }
631
632 public void setDragging(final boolean dragging) {
633 this.dragging = dragging;
634 }
635
636 public void setSelected(final boolean selected) {
637 if (this.isEnabled()) {
638 this.selected = selected;
639 } else {
640 this.selected = false;
641 }
642 addRemGraph();
643 }
644
645 private void addRemGraph() {
646 if (selected) {
647 this.setForeground(GDE.getDisplayColor());
648 this.repaint();
649 OpenLogViewer.getInstance().getMultiGraphLayeredPane().addGraph(this.getName());
650 } else {
651 this.setForeground(GDE.getDisplayColor().darker().darker());
652 if (OpenLogViewer.getInstance().getMultiGraphLayeredPane().removeGraph(this.getName())) {
653 OpenLogViewer.getInstance().getMultiGraphLayeredPane().repaint();
654 }
655 }
656 }
657
658 @Override
659 public int compareTo(final Object o) {
660 if (o instanceof ActiveHeaderLabel) {
661 ActiveHeaderLabel GCB = (ActiveHeaderLabel) o;
662 return this.GDE.compareTo(GCB.getGDE());
663 } else {
664 return -1;
665 }
666 }
667 }
668 }