]> gerrit.simantics Code Review - simantics/district.git/blob - org.simantics.district.network.ui/src/org/simantics/district/network/ui/visualisations/DynamicVisualisationsUI.java
Minor UI bug fixes for dynamic visualisations
[simantics/district.git] / org.simantics.district.network.ui / src / org / simantics / district / network / ui / visualisations / DynamicVisualisationsUI.java
1 package org.simantics.district.network.ui.visualisations;
2
3 import java.util.ArrayList;
4 import java.util.Collection;
5 import java.util.HashMap;
6 import java.util.List;
7 import java.util.Map;
8 import java.util.Map.Entry;
9 import java.util.Objects;
10 import java.util.Optional;
11 import java.util.function.Supplier;
12 import java.util.stream.Collectors;
13 import java.util.stream.Stream;
14
15 import org.eclipse.jface.dialogs.Dialog;
16 import org.eclipse.jface.dialogs.IInputValidator;
17 import org.eclipse.jface.dialogs.InputDialog;
18 import org.eclipse.jface.layout.GridDataFactory;
19 import org.eclipse.jface.layout.GridLayoutFactory;
20 import org.eclipse.swt.SWT;
21 import org.eclipse.swt.events.FocusAdapter;
22 import org.eclipse.swt.events.FocusEvent;
23 import org.eclipse.swt.events.KeyAdapter;
24 import org.eclipse.swt.events.KeyEvent;
25 import org.eclipse.swt.events.ModifyEvent;
26 import org.eclipse.swt.events.ModifyListener;
27 import org.eclipse.swt.events.SelectionAdapter;
28 import org.eclipse.swt.events.SelectionEvent;
29 import org.eclipse.swt.widgets.Button;
30 import org.eclipse.swt.widgets.Combo;
31 import org.eclipse.swt.widgets.Composite;
32 import org.eclipse.swt.widgets.Display;
33 import org.eclipse.swt.widgets.Group;
34 import org.eclipse.swt.widgets.Label;
35 import org.eclipse.swt.widgets.Shell;
36 import org.eclipse.swt.widgets.Text;
37 import org.eclipse.swt.widgets.Widget;
38 import org.simantics.Simantics;
39 import org.simantics.db.ReadGraph;
40 import org.simantics.db.Resource;
41 import org.simantics.db.WriteGraph;
42 import org.simantics.db.common.NamedResource;
43 import org.simantics.db.common.request.UniqueRead;
44 import org.simantics.db.common.request.WriteRequest;
45 import org.simantics.db.exception.DatabaseException;
46 import org.simantics.db.layer0.util.RemoverUtil;
47 import org.simantics.db.procedure.Listener;
48 import org.simantics.district.network.DistrictNetworkUtil;
49 import org.simantics.district.network.profile.ActiveDynamicVisualisationsRequest;
50 import org.simantics.district.network.profile.DynamicVisualisationsRequest;
51 import org.simantics.district.network.visualisations.DynamicVisualisationsContributions;
52 import org.simantics.district.network.visualisations.DynamicVisualisationsContributions.DynamicColoringObject;
53 import org.simantics.district.network.visualisations.DynamicVisualisationsContributions.DynamicSizingObject;
54 import org.simantics.district.network.visualisations.model.ColorBarOptions;
55 import org.simantics.district.network.visualisations.model.ColorBarOptions.ColorBarsLocation;
56 import org.simantics.district.network.visualisations.model.ColorBarOptions.ColorBarsSize;
57 import org.simantics.district.network.visualisations.model.DynamicColorContribution;
58 import org.simantics.district.network.visualisations.model.DynamicColorMap;
59 import org.simantics.district.network.visualisations.model.DynamicSizeContribution;
60 import org.simantics.district.network.visualisations.model.DynamicSizeMap;
61 import org.simantics.district.network.visualisations.model.DynamicVisualisation;
62 import org.simantics.district.network.visualisations.model.SizeBarOptions;
63 import org.simantics.district.network.visualisations.model.SizeBarOptions.SizeBarsLocation;
64 import org.simantics.district.network.visualisations.model.SizeBarOptions.SizeBarsSize;
65 import org.simantics.utils.datastructures.Pair;
66 import org.simantics.utils.ui.dialogs.ShowError;
67 import org.slf4j.Logger;
68 import org.slf4j.LoggerFactory;
69
70 public class DynamicVisualisationsUI extends Composite {
71
72     private static final Logger LOGGER = LoggerFactory.getLogger(DynamicVisualisationsUI.class);
73
74     private Resource parentResource;
75     private VisualisationListener listener;
76     private DynamicVisualisation visualisation;
77
78     private Button showSizeButton;
79     private Button sizeTicksButton;
80     private Button sizeGradientButton;
81     private Combo sizeLocationCombo;
82     private Combo sizeSizeCombo;
83     private Button showColorButton;
84     private Button colorTicksButton;
85     private Button colorGradientButton;
86     private Combo colorLocationCombo;
87     private Combo colorSizeCombo;
88
89     private Combo templateSelectionCombo;
90
91     private List<Supplier<Pair<String, DynamicColorContribution>>> colorSuppliers;
92
93     private Button removeVisualisationTemplateButton;
94
95     public DynamicVisualisationsUI(Composite parent, int style) {
96         super(parent, style);
97 //        ScrolledComposite scrolledComposite = new ScrolledComposite(this, SWT.V_SCROLL);
98 //        scrolledComposite.setLayout(new GridLayout(1, false));
99 //        scrolledComposite.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
100 //
101 //        Composite firstContent = new Composite(scrolledComposite, SWT.NONE);
102 //        firstContent.setLayout(new GridLayout(1, false));
103 //        firstContent.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
104         
105         defaultInitializeUI(this);
106         
107 //        scrolledComposite.setContent(firstContent);
108 //        scrolledComposite.setExpandHorizontal(true);
109 //        scrolledComposite.setExpandVertical(true);
110 //        scrolledComposite.setMinSize(firstContent.computeSize(SWT.DEFAULT, SWT.DEFAULT));
111         
112     }
113
114     private void defaultInitializeUI(Composite parent) {
115         
116         GridDataFactory.fillDefaults().grab(true, true).applyTo(parent);
117         GridLayoutFactory.fillDefaults().numColumns(1).margins(5, 5).applyTo(parent);
118         
119         Composite selectionComposite = new Composite(parent, SWT.NONE);
120         GridDataFactory.fillDefaults().grab(true, false).applyTo(selectionComposite);
121         GridLayoutFactory.fillDefaults().numColumns(2).margins(5, 5).applyTo(selectionComposite);
122         
123         Label templateNameLabel = new Label(selectionComposite, SWT.NONE);
124         templateNameLabel.setText("Visualisation template");
125         templateSelectionCombo = new Combo(selectionComposite, SWT.READ_ONLY);
126         GridDataFactory.fillDefaults().grab(true, false).applyTo(templateSelectionCombo);
127         templateSelectionCombo.addSelectionListener(new SelectionAdapter() {
128             
129             @Override
130             public void widgetSelected(SelectionEvent e) {
131                 String item = templateSelectionCombo.getItem(templateSelectionCombo.getSelectionIndex());
132                 for (NamedResource template : visualisations) {
133                     if (item.equals(template.getName())) {
134                         Simantics.getSession().asyncRequest(new WriteRequest() {
135                             
136                             @Override
137                             public void perform(WriteGraph graph) throws DatabaseException {
138                                 Resource vf = DistrictNetworkUtil.getVisualisationFolder(graph, parentResource);
139                                 DistrictNetworkUtil.setActiveVisualisation(graph, vf, template.getResource());
140                             }
141                         });
142                         break;
143                     }
144                 }
145             }
146         });
147         
148         Composite coloringObjectsComposite = new Composite(parent, SWT.NONE);
149         GridDataFactory.fillDefaults().grab(true, false).applyTo(coloringObjectsComposite);
150         GridLayoutFactory.fillDefaults().numColumns(1).applyTo(coloringObjectsComposite);
151         initializeColoringObjects(coloringObjectsComposite);
152         
153         Composite colorBarsComposite = new Composite(parent, SWT.NONE);
154         GridDataFactory.fillDefaults().grab(true, false).applyTo(colorBarsComposite);
155         GridLayoutFactory.fillDefaults().numColumns(1).applyTo(colorBarsComposite);
156         initializeColorBars(colorBarsComposite);
157         
158         Composite objectSizesComposite = new Composite(parent, SWT.NONE);
159         GridDataFactory.fillDefaults().grab(true, false).applyTo(objectSizesComposite);
160         GridLayoutFactory.fillDefaults().numColumns(1).applyTo(objectSizesComposite);
161         initializeObjectSizes(objectSizesComposite);
162         
163         Composite sizeBarsComposite = new Composite(parent, SWT.NONE);
164         GridDataFactory.fillDefaults().grab(true, false).applyTo(sizeBarsComposite);
165         GridLayoutFactory.fillDefaults().numColumns(1).applyTo(sizeBarsComposite);
166         initializeSizeBars(sizeBarsComposite);
167         
168         Composite buttonBarsComposite = new Composite(parent, SWT.NONE);
169         GridDataFactory.fillDefaults().grab(true, false).applyTo(buttonBarsComposite);
170         GridLayoutFactory.fillDefaults().numColumns(3).applyTo(buttonBarsComposite);
171         
172         Button saveVisualisationTemplateAsButton = new Button(buttonBarsComposite, SWT.NONE);
173         saveVisualisationTemplateAsButton.setText("Save as visualisation template");
174         saveVisualisationTemplateAsButton.addSelectionListener(new SelectionAdapter() {
175             
176             @Override
177             public void widgetSelected(SelectionEvent e) {
178                 showSaveVisualisationTemplateDialog(e.widget.getDisplay().getActiveShell());
179             }
180         });
181         
182         removeVisualisationTemplateButton = new Button(buttonBarsComposite, SWT.NONE);
183         removeVisualisationTemplateButton.setText("Remove");
184         removeVisualisationTemplateButton.setEnabled(visualisation != null && visualisation.getVisualisationResource() != null);
185         removeVisualisationTemplateButton.addSelectionListener(new SelectionAdapter() {
186             
187             @Override
188             public void widgetSelected(SelectionEvent e) {
189                 removeVisualisationTemplate(visualisation.getName(), Optional.of(visualisation.getVisualisationResource()));
190             }
191         });
192         
193     }
194     
195     protected void removeVisualisationTemplate(String name, Optional<Resource> of) {
196         if (of.isPresent()) {
197             Resource visualisation = of.get();
198             Simantics.getSession().asyncRequest(new WriteRequest() {
199                 
200                 @Override
201                 public void perform(WriteGraph graph) throws DatabaseException {
202                     RemoverUtil.remove(graph, visualisation);
203                 }
204             });
205         }
206     }
207
208     private void showSaveVisualisationTemplateDialog(Shell shell) {
209         
210         InputDialog dialog = new InputDialog(shell, "Save visualisation template", "Give template a name", "", new IInputValidator() {
211             
212             @Override
213             public String isValid(String newText) {
214                 if (newText == null || newText.isEmpty())
215                     return "Name cannot be empty";
216                 return null;
217             }
218         });
219         
220         if (dialog.open() == Dialog.OK) {
221             String name = dialog.getValue();
222             try {
223                 persistVisualisationTemplate(name, Optional.empty());
224             } catch (Exception e) {
225                 LOGGER.error("Could not persist visualisation template", e);
226                 ShowError.showError("Could not persist visualisation template", e.getMessage(), e);
227             }
228         }
229     }
230
231     private void persistCurrentVisualisationTemplateIfAvailable() {
232         if (visualisation != null) {
233             try {
234                 persistVisualisationTemplate(visualisation.getName(), Optional.of(visualisation.getVisualisationResource()));
235             } catch (Exception e1) {
236                 LOGGER.error("Could not persist visualisation template", e1);
237                 ShowError.showError("Could not persist visualisation template", e1.getMessage(), e1);
238             }
239         } else {
240             LOGGER.info("No current visualisation template selected for saving");
241         }
242     }
243     
244     private void persistVisualisationTemplate(String templateName, Optional<Resource> existing) throws Exception {
245         
246         List<Pair<String, DynamicColorContribution>> colorCollect = colorSuppliers.stream().map(s -> s.get()).filter(Objects::nonNull).collect(Collectors.toList());
247
248         String colorLocation = colorLocationCombo.getItem(colorLocationCombo.getSelectionIndex());
249         String colorSize = colorSizeCombo.getItem(colorSizeCombo.getSelectionIndex());
250         
251         ColorBarOptions colorBarOptions = new ColorBarOptions()
252                 .showColorBars(showColorButton.getSelection())
253                 .showColorBarsTicks(colorTicksButton.getSelection())
254                 .useGradients(colorGradientButton.getSelection())
255                 .withLocation(ColorBarsLocation.valueOf(colorLocation))
256                 .withSize(ColorBarsSize.valueOf(colorSize));
257         
258         List<Pair<String, DynamicSizeContribution>> sizeCollect = sizeSuppliers.stream().map(s -> s.get()).filter(Objects::nonNull).collect(Collectors.toList());
259         
260         String sizeLocation = sizeLocationCombo.getItem(sizeLocationCombo.getSelectionIndex());
261         String sizeSize = sizeSizeCombo.getItem(sizeSizeCombo.getSelectionIndex());
262         
263         SizeBarOptions sizeBarOptions = new SizeBarOptions()
264                 .showSizeBars(showSizeButton.getSelection())
265                 .showSizeBarsTicks(sizeTicksButton.getSelection())
266                 .useGradients(sizeGradientButton.getSelection())
267                 .withLocation(SizeBarsLocation.valueOf(sizeLocation))
268                 .withSize(SizeBarsSize.valueOf(sizeSize));
269         
270         Simantics.getSession().asyncRequest(new WriteRequest() {
271             
272             @Override
273             public void perform(WriteGraph graph) throws DatabaseException {
274                 Resource exist;
275                 if (existing.isPresent()) {
276                     exist = existing.get();
277                 } else {
278                     exist = DistrictNetworkUtil.createVisualisation(graph, parentResource, templateName);
279                 }
280                 
281                 DistrictNetworkUtil.setColorContributions(graph, exist, colorCollect);
282                 
283                 DistrictNetworkUtil.setColorBarOptions(graph, exist, colorBarOptions);
284                 DistrictNetworkUtil.setSizeContributions(graph, exist, sizeCollect);
285                 DistrictNetworkUtil.setSizeBarOptions(graph, exist, sizeBarOptions);
286             }
287         });
288     }
289
290     private void initializeColoringObjects(Composite parent) {
291         Group group = new Group(parent, SWT.NONE);
292         group.setText("Coloring Objects");
293         GridDataFactory.fillDefaults().grab(true, false).applyTo(group);
294         GridLayoutFactory.fillDefaults().numColumns(8).margins(5, 5).applyTo(group);
295         
296         {
297             createColoringObjectHeaderRow(group);
298         }
299         colorSuppliers = new ArrayList<>();
300         {
301             try {
302                 Pair<Collection<DynamicColoringObject>, Map<String, DynamicColorMap>> result = Simantics.getSession().syncRequest(new UniqueRead<Pair<Collection<DynamicColoringObject>, Map<String, DynamicColorMap>>>() {
303
304                     @Override
305                     public Pair<Collection<DynamicColoringObject>, Map<String, DynamicColorMap>> perform(ReadGraph graph) throws DatabaseException {
306                         Map<String, DynamicColorMap> dynamicColorMaps = DynamicVisualisationsContributions.dynamicColorMaps(graph);
307                         return Pair.make(DynamicVisualisationsContributions.dynamicColoringObjects(graph), dynamicColorMaps);
308                     }
309                 });
310                 
311                 for (DynamicColoringObject object : result.first) {
312                     colorSuppliers.add(createColoringObjectRow(group, object, result.second));
313                 }
314
315             } catch (DatabaseException e) {
316                 LOGGER.error("Could not create coloring objecst", e);
317             }
318         }
319     }
320     
321     private void createColoringObjectHeaderRow(Composite parent) {
322
323         Label label = new Label(parent, SWT.NONE);
324         label.setText("Label");
325         GridDataFactory.fillDefaults().grab(true, false).align(SWT.CENTER, SWT.CENTER).applyTo(label);
326         
327         label = new Label(parent, SWT.NONE);
328         label.setText("Used");
329         GridDataFactory.fillDefaults().grab(true, false).align(SWT.CENTER, SWT.CENTER).applyTo(label);
330         
331         label = new Label(parent, SWT.NONE);
332         label.setText("Variable");
333         GridDataFactory.fillDefaults().grab(true, false).align(SWT.CENTER, SWT.CENTER).applyTo(label);
334         
335         label = new Label(parent, SWT.NONE);
336         label.setText("Min");
337         GridDataFactory.fillDefaults().grab(true, false).align(SWT.CENTER, SWT.CENTER).applyTo(label);
338         
339         label = new Label(parent, SWT.NONE);
340         label.setText("Max");
341         GridDataFactory.fillDefaults().grab(true, false).align(SWT.CENTER, SWT.CENTER).applyTo(label);
342         
343         label = new Label(parent, SWT.NONE);
344         label.setText("Unit");
345         GridDataFactory.fillDefaults().grab(true, false).align(SWT.CENTER, SWT.CENTER).applyTo(label);
346         
347         label = new Label(parent, SWT.NONE);
348         label.setText("ColorMap");
349         GridDataFactory.fillDefaults().grab(true, false).align(SWT.CENTER, SWT.CENTER).applyTo(label);
350         
351         label = new Label(parent, SWT.NONE);
352         label.setText("Default");
353         GridDataFactory.fillDefaults().grab(true, false).align(SWT.CENTER, SWT.CENTER).applyTo(label);
354     }
355
356     private Map<String, ColoringObjectRow> coloringRows = new HashMap<>();
357     private Map<String, SizingObjectRow> sizingRows = new HashMap<>();
358
359     private VisualisationsListener visualisationsListener;
360
361     private Collection<NamedResource> visualisations;
362
363     private List<Supplier<Pair<String, DynamicSizeContribution>>> sizeSuppliers;
364
365     private static class ColoringObjectRow {
366         
367         private final Label label;
368         private final Button usedButton;
369         private final Combo variableCombo;
370         private final Text minText;
371         private final Text maxText;
372         private final Text unit;
373         private final Combo colorMapCombo;
374         private final Button defaultButton;
375         
376         public ColoringObjectRow(Label label, Button usedButton, Combo variableCombo, Text minText, Text maxText, Text unit,
377                 Combo colorMapCombo, Button defaultButton) {
378             super();
379             this.label = label;
380             this.usedButton = usedButton;
381             this.variableCombo = variableCombo;
382             this.minText = minText;
383             this.maxText = maxText;
384             this.unit = unit;
385             this.colorMapCombo = colorMapCombo;
386             this.defaultButton = defaultButton;
387         }
388
389         public void update(DynamicColorContribution colorContribution) {
390             String[] items = variableCombo.getItems();
391             for (int i = 0; i < items.length; i++) {
392                 if (colorContribution.getLabel().equals(items[i])) {
393                     variableCombo.select(i);
394                     break;
395                 }
396             }
397             minText.setText(Double.toString(colorContribution.getDefaultMin()));
398             maxText.setText(Double.toString(colorContribution.getDefaultMax()));
399             unit.setText(colorContribution.getUnit());
400             
401             String[] colorItems = colorMapCombo.getItems();
402             for (int i = 0; i < colorItems.length; i++) {
403                 
404                 if (colorContribution.getDefaultColorMap().getLabel().equals(colorItems[i])) {
405                     colorMapCombo.select(i);
406                     break;
407                 }
408             }
409             usedButton.setSelection(colorContribution.isUsed());
410             defaultButton.setSelection(colorContribution.isUseDefault());
411             
412             minText.setEnabled(!colorContribution.isUseDefault());
413             maxText.setEnabled(!colorContribution.isUseDefault());
414             colorMapCombo.setEnabled(!colorContribution.isUseDefault());
415         }
416     }
417
418     private static class SizingObjectRow {
419         
420         private final Label label;
421         private final Button usedButton;
422         private final Combo variableCombo;
423         private final Text minText;
424         private final Text maxText;
425         private final Label unit;
426         private final Combo sizeMapCombo;
427         private final Button defaultButton;
428         
429         public SizingObjectRow(Label label, Button usedButton, Combo variableCombo, Text minText, Text maxText, Label unit,
430                 Combo sizeMapCombo, Button defaultButton) {
431             super();
432             this.label = label;
433             this.usedButton = usedButton;
434             this.variableCombo = variableCombo;
435             this.minText = minText;
436             this.maxText = maxText;
437             this.unit = unit;
438             this.sizeMapCombo = sizeMapCombo;
439             this.defaultButton = defaultButton;
440         }
441
442         public void update(DynamicSizeContribution sizeContribution) {
443             String[] items = variableCombo.getItems();
444             for (int i = 0; i < items.length; i++) {
445                 if (sizeContribution.getLabel().equals(items[i])) {
446                     variableCombo.select(i);
447                     break;
448                 }
449             }
450             minText.setText(Double.toString(sizeContribution.getDefaultMin()));
451             maxText.setText(Double.toString(sizeContribution.getDefaultMax()));
452             unit.setText(sizeContribution.getUnit());
453             
454             String[] sizeItems = sizeMapCombo.getItems();
455             for (int i = 0; i < sizeItems.length; i++) {
456                 if (sizeContribution.getDefaultSizeMap().getLabel().equals(sizeItems[i])) {
457                     sizeMapCombo.select(i);
458                     break;
459                 }
460             }
461             usedButton.setSelection(sizeContribution.isUsed());
462             defaultButton.setSelection(sizeContribution.isUseDefault());
463             
464             minText.setEnabled(!sizeContribution.isUseDefault());
465             maxText.setEnabled(!sizeContribution.isUseDefault());
466             sizeMapCombo.setEnabled(!sizeContribution.isUseDefault());
467         }
468     }
469
470     private Supplier<Pair<String, DynamicColorContribution>> createColoringObjectRow(Composite parent, DynamicColoringObject object, Map<String, DynamicColorMap> colorMaps) {
471         Label label = new Label(parent, SWT.NONE);
472         label.setText(object.getColoringObject().getName());
473         GridDataFactory.fillDefaults().align(SWT.CENTER, SWT.CENTER).applyTo(label);
474         
475         Map<String, DynamicColorContribution> colorContributions = object.getColorContributions();
476         
477         Button usedButton = new Button(parent, SWT.CHECK);
478         GridDataFactory.fillDefaults().align(SWT.CENTER, SWT.CENTER).applyTo(usedButton);
479         addSelectionListener(usedButton);
480         
481         Combo variableCombo = new Combo(parent, SWT.READ_ONLY);
482         variableCombo.setItems(colorContributions.keySet().toArray(new String[colorContributions.size()]));
483         addSelectionListener(variableCombo);
484         
485         GridDataFactory.fillDefaults().align(SWT.CENTER, SWT.CENTER).applyTo(variableCombo);
486         
487         Text minText = new Text(parent, SWT.BORDER);
488         GridDataFactory.fillDefaults().grab(true, false).hint(150, SWT.DEFAULT).align(SWT.CENTER, SWT.CENTER).applyTo(minText);
489         addSelectionListener(minText);
490         
491         Text maxText = new Text(parent, SWT.BORDER);
492         GridDataFactory.fillDefaults().grab(true, false).hint(150, SWT.DEFAULT).align(SWT.CENTER, SWT.CENTER).applyTo(maxText);
493         addSelectionListener(maxText);
494         
495         Text unit = new Text(parent, SWT.READ_ONLY);
496         GridDataFactory.fillDefaults().grab(true, false).align(SWT.CENTER, SWT.CENTER).applyTo(unit);
497         
498         Combo colorMapCombo = new Combo(parent, SWT.READ_ONLY);
499         GridDataFactory.fillDefaults().align(SWT.CENTER, SWT.CENTER).applyTo(colorMapCombo);
500         colorMapCombo.setItems(colorMaps.keySet().toArray(new String[colorMaps.keySet().size()]));
501         addSelectionListener(colorMapCombo);
502         
503         Button defaultButton = new Button(parent, SWT.CHECK);
504         GridDataFactory.fillDefaults().align(SWT.CENTER, SWT.CENTER).applyTo(defaultButton);
505         addSelectionListener(defaultButton);
506         defaultButton.addSelectionListener(new SelectionAdapter() {
507             
508             @Override
509             public void widgetSelected(SelectionEvent e) {
510                 int index = variableCombo.getSelectionIndex();
511                 if (index >= 0) {
512                     String key = variableCombo.getItem(index);
513                     DynamicColorContribution cont = colorContributions.get(key);
514                     
515                     minText.setText(Double.toString(cont.getDefaultMin()));
516                     minText.setEnabled(!defaultButton.getSelection());
517                     maxText.setText(Double.toString(cont.getDefaultMax()));
518                     maxText.setEnabled(!defaultButton.getSelection());
519                     unit.setText(cont.getUnit());
520                     
521                     String[] items = colorMapCombo.getItems();
522                     for (int i = 0; i < items.length; i++) {
523                         String item = items[i];
524                         if (item.equals(cont.getDefaultColorMap().getLabel())) {
525                             colorMapCombo.select(i);
526                             break;
527                         }
528                     }
529                     colorMapCombo.setEnabled(!defaultButton.getSelection());
530                 }
531             }
532         });
533         
534         variableCombo.addSelectionListener(new SelectionAdapter() {
535             
536             @Override
537             public void widgetSelected(SelectionEvent e) {
538                 // handle update for others
539                 int index = variableCombo.getSelectionIndex();
540                 if (index >= 0) {
541                     String key = variableCombo.getItem(index);
542                     DynamicColorContribution cont = colorContributions.get(key);
543                     
544                     if (minText.getText().isEmpty()) {
545                         minText.setText(Double.toString(cont.getDefaultMin()));
546                     }
547                     if (maxText.getText().isEmpty()) {
548                         maxText.setText(Double.toString(cont.getDefaultMax()));
549                     }
550                     unit.setText(cont.getUnit());
551                     
552                     String[] items = colorMapCombo.getItems();
553                     for (int i = 0; i < items.length; i++) {
554                         String item = items[i];
555                         if (item.equals(cont.getDefaultColorMap().getLabel())) {
556                             colorMapCombo.select(i);
557                             break;
558                         }
559                     }
560                     
561                     defaultButton.setSelection(true);
562                 }
563             }
564         });
565         
566         coloringRows.put(object.getColoringObject().getName(), new ColoringObjectRow(label, usedButton, variableCombo, minText, maxText, unit, colorMapCombo, defaultButton));
567
568         return new Supplier<Pair<String, DynamicColorContribution>>() {
569
570             @Override
571             public Pair<String, DynamicColorContribution> get() {
572                 int selectionIndex = variableCombo.getSelectionIndex();
573                 if (selectionIndex >= 0) {
574                     String key = variableCombo.getItem(selectionIndex);
575                     DynamicColorContribution cont = colorContributions.get(key);
576                     if (cont != null) {
577                         String colorMap = colorMapCombo.getItem(colorMapCombo.getSelectionIndex());
578                         try {
579                             Map<String, DynamicColorMap> colorMaps = Simantics.getSession().syncRequest(new UniqueRead<Map<String, DynamicColorMap>>() {
580         
581                                 @Override
582                                 public Map<String, DynamicColorMap> perform(ReadGraph graph) throws DatabaseException {
583                                     return DynamicVisualisationsContributions.dynamicColorMaps(graph);
584                                 }
585                             });
586                             DynamicColorMap dColorMap = colorMaps.get(colorMap);
587                             String label = variableCombo.getItem(variableCombo.getSelectionIndex());
588                             
589                             DynamicColorContribution dcc = new DynamicColorContribution(label, cont.getModuleName(), cont.getAttributeName(), unit.getText(), cont.getVariableGain(), cont.getVariableBias(), dColorMap, Double.parseDouble(minText.getText()), Double.parseDouble(maxText.getText()));
590                             dcc.setUsed(usedButton.getSelection());
591                             dcc.setUseDefault(defaultButton.getSelection());
592                             
593                             return Pair.make(object.getColoringObject().getName(), dcc);
594                         } catch (DatabaseException e) {
595                             LOGGER.error("Could not get DynamicColorContribution", e);
596                         }
597                     }
598                 }
599                 return null;
600             }
601         };
602     }
603     
604     private void createSizingObjectHeaderRow(Composite parent) {
605
606         Label label = new Label(parent, SWT.NONE);
607         label.setText("Label");
608         GridDataFactory.fillDefaults().grab(true, false).align(SWT.CENTER, SWT.CENTER).applyTo(label);
609         
610         label = new Label(parent, SWT.NONE);
611         label.setText("Used");
612         GridDataFactory.fillDefaults().grab(true, false).align(SWT.CENTER, SWT.CENTER).applyTo(label);
613         
614         label = new Label(parent, SWT.NONE);
615         label.setText("Variable");
616         GridDataFactory.fillDefaults().grab(true, false).align(SWT.CENTER, SWT.CENTER).applyTo(label);
617         
618         label = new Label(parent, SWT.NONE);
619         label.setText("Min");
620         GridDataFactory.fillDefaults().grab(true, false).align(SWT.CENTER, SWT.CENTER).applyTo(label);
621         
622         label = new Label(parent, SWT.NONE);
623         label.setText("Max");
624         GridDataFactory.fillDefaults().grab(true, false).align(SWT.CENTER, SWT.CENTER).applyTo(label);
625         
626         label = new Label(parent, SWT.NONE);
627         label.setText("Unit");
628         GridDataFactory.fillDefaults().grab(true, false).align(SWT.CENTER, SWT.CENTER).applyTo(label);
629         
630         label = new Label(parent, SWT.NONE);
631         label.setText("SizeMap");
632         GridDataFactory.fillDefaults().grab(true, false).align(SWT.CENTER, SWT.CENTER).applyTo(label);
633         
634         label = new Label(parent, SWT.NONE);
635         label.setText("Default");
636         GridDataFactory.fillDefaults().grab(true, false).align(SWT.CENTER, SWT.CENTER).applyTo(label);
637     }
638
639     private Supplier<Pair<String, DynamicSizeContribution>> createSizingObjectRow(Composite parent, DynamicSizingObject object, Map<String, DynamicSizeMap> sizeMaps) {
640         Label label = new Label(parent, SWT.NONE);
641         label.setText(object.getSizingObject().getName());
642         GridDataFactory.fillDefaults().align(SWT.CENTER, SWT.CENTER).applyTo(label);
643         
644         Map<String, DynamicSizeContribution> sizeContributions = object.getSizeContributions();
645         
646         Button usedButton = new Button(parent, SWT.CHECK);
647         GridDataFactory.fillDefaults().align(SWT.CENTER, SWT.CENTER).applyTo(usedButton);
648         addSelectionListener(usedButton);
649         
650         Combo variableCombo = new Combo(parent, SWT.READ_ONLY);
651         variableCombo.setItems(sizeContributions.keySet().toArray(new String[sizeContributions.size()]));
652         addSelectionListener(variableCombo);
653         
654         GridDataFactory.fillDefaults().align(SWT.CENTER, SWT.CENTER).applyTo(variableCombo);
655         
656         Text minText = new Text(parent, SWT.BORDER);
657         GridDataFactory.fillDefaults().align(SWT.CENTER, SWT.CENTER).applyTo(minText);
658         addSelectionListener(minText);
659         
660         Text maxText = new Text(parent, SWT.BORDER);
661         GridDataFactory.fillDefaults().align(SWT.CENTER, SWT.CENTER).applyTo(maxText);
662         addSelectionListener(maxText);
663         
664         Label unit = new Label(parent, SWT.NONE);
665         GridDataFactory.fillDefaults().grab(true, false).align(SWT.CENTER, SWT.CENTER).applyTo(unit);
666         
667         Combo sizeMapCombo = new Combo(parent, SWT.READ_ONLY);
668         GridDataFactory.fillDefaults().align(SWT.CENTER, SWT.CENTER).applyTo(sizeMapCombo);
669         sizeMapCombo.setItems(sizeMaps.keySet().toArray(new String[sizeMaps.keySet().size()]));
670         addSelectionListener(sizeMapCombo);
671         
672         Button defaultButton = new Button(parent, SWT.CHECK);
673         GridDataFactory.fillDefaults().align(SWT.CENTER, SWT.CENTER).applyTo(defaultButton);
674         addSelectionListener(defaultButton);
675         defaultButton.addSelectionListener(new SelectionAdapter() {
676             
677             @Override
678             public void widgetSelected(SelectionEvent e) {
679                 int index = variableCombo.getSelectionIndex();
680                 if (index >= 0) {
681                     String key = variableCombo.getItem(index);
682                     DynamicSizeContribution cont = sizeContributions.get(key);
683                     
684                     minText.setText(Double.toString(cont.getDefaultMin()));
685                     minText.setEnabled(!defaultButton.getSelection());
686                     maxText.setText(Double.toString(cont.getDefaultMax()));
687                     maxText.setEnabled(!defaultButton.getSelection());
688                     unit.setText(cont.getUnit());
689                     
690                     String[] items = sizeMapCombo.getItems();
691                     for (int i = 0; i < items.length; i++) {
692                         String item = items[i];
693                         if (item.equals(cont.getDefaultSizeMap().getLabel())) {
694                             sizeMapCombo.select(i);
695                             break;
696                         }
697                     }
698                     sizeMapCombo.setEnabled(!defaultButton.getSelection());
699                 }
700             }
701         });
702         
703         variableCombo.addSelectionListener(new SelectionAdapter() {
704             
705             @Override
706             public void widgetSelected(SelectionEvent e) {
707                 // handle update for others
708                 String key = variableCombo.getItem(variableCombo.getSelectionIndex());
709                 DynamicSizeContribution cont = sizeContributions.get(key);
710                 
711                 if (minText.getText().isEmpty()) {
712                     minText.setText(Double.toString(cont.getDefaultMin()));
713                 }
714                 if (maxText.getText().isEmpty()) {
715                     maxText.setText(Double.toString(cont.getDefaultMax()));
716                 }
717                 unit.setText(cont.getUnit());
718                 
719                 String[] items = sizeMapCombo.getItems();
720                 for (int i = 0; i < items.length; i++) {
721                     String item = items[i];
722                     if (item.equals(cont.getDefaultSizeMap().getLabel())) {
723                         sizeMapCombo.select(i);
724                         break;
725                     }
726                 }
727                 
728                 defaultButton.setSelection(true);
729             }
730         });
731         
732         sizingRows.put(object.getSizingObject().getName(), new SizingObjectRow(label, usedButton, variableCombo, minText, maxText, unit, sizeMapCombo, defaultButton));
733         
734         return new Supplier<Pair<String, DynamicSizeContribution>>() {
735
736             @Override
737             public Pair<String, DynamicSizeContribution> get() {
738                 int selectionIndex = variableCombo.getSelectionIndex();
739                 if (selectionIndex >= 0) {
740                     String key = variableCombo.getItem(selectionIndex);
741                     DynamicSizeContribution cont = sizeContributions.get(key);
742                     if (cont != null) {
743                         String sizeMap = sizeMapCombo.getItem(sizeMapCombo.getSelectionIndex());
744                         try {
745                             Map<String, DynamicSizeMap> sizeMaps = Simantics.getSession().syncRequest(new UniqueRead<Map<String, DynamicSizeMap>>() {
746         
747                                 @Override
748                                 public Map<String, DynamicSizeMap> perform(ReadGraph graph) throws DatabaseException {
749                                     return DynamicVisualisationsContributions.dynamicSizeMaps(graph);
750                                 }
751                             });
752                             DynamicSizeMap dColorMap = sizeMaps.get(sizeMap);
753                             String label = variableCombo.getItem(variableCombo.getSelectionIndex());
754                             
755                             DynamicSizeContribution dsc = new DynamicSizeContribution(label, cont.getModuleName(), cont.getAttributeName(), unit.getText(), cont.getVariableGain(), cont.getVariableBias(), dColorMap, Double.parseDouble(minText.getText()), Double.parseDouble(maxText.getText()));
756                             dsc.setUsed(usedButton.getSelection());
757                             dsc.setUseDefault(defaultButton.getSelection());
758                             
759                             return Pair.make(object.getSizingObject().getName(), dsc);
760                         } catch (DatabaseException e) {
761                             LOGGER.error("Could not get DynamicColorContribution", e);
762                         }
763                     }
764                 }
765                 return null;
766             }
767         };
768     }
769     
770     private void initializeColorBars(Composite parent) {
771         Group group = new Group(parent, SWT.NONE);
772         group.setText("Color Bars");
773         GridDataFactory.fillDefaults().grab(true, false).applyTo(group);
774         GridLayoutFactory.fillDefaults().numColumns(8).margins(5, 5).applyTo(group);
775         
776         createColorBars(group);
777     }
778     
779     private void createColorBars(Composite parent) {
780         
781         showColorButton = new Button(parent, SWT.CHECK);
782         showColorButton.setText("Show");
783         addSelectionListener(showColorButton);
784         
785         colorTicksButton = new Button(parent, SWT.CHECK);
786         colorTicksButton.setText("Ticks");
787         addSelectionListener(colorTicksButton);
788         
789         colorGradientButton = new Button(parent, SWT.CHECK);
790         colorGradientButton.setText("Gradients");
791         addSelectionListener(colorGradientButton);
792         
793         Label label = new Label(parent, SWT.NONE);
794         label.setText("Location");
795         colorLocationCombo = new Combo(parent, SWT.READ_ONLY);
796         String[] colorLocationItems = Stream.of(ColorBarsLocation.values()).map(size -> size.toString()).toArray(String[]::new);
797         colorLocationCombo.setItems(colorLocationItems);
798         if (colorLocationItems.length > 0) {
799             colorLocationCombo.select(0);
800         }
801         addSelectionListener(colorLocationCombo);
802         
803         label = new Label(parent, SWT.NONE);
804         label.setText("Size");
805         colorSizeCombo = new Combo(parent, SWT.READ_ONLY);
806         String[] colorSizeItems = Stream.of(ColorBarsSize.values()).map(size -> size.toString()).toArray(String[]::new);
807         colorSizeCombo.setItems(colorSizeItems);
808         if (colorSizeItems.length > 0) {
809             colorSizeCombo.select(0);
810         }
811         addSelectionListener(colorSizeCombo);
812     }
813
814     private void initializeObjectSizes(Composite parent) {
815         Group group = new Group(parent, SWT.NONE);
816         group.setText("Object Sizes");
817         GridDataFactory.fillDefaults().grab(true, false).applyTo(group);
818         GridLayoutFactory.fillDefaults().numColumns(8).margins(5, 5).applyTo(group);
819         
820         {
821             createSizingObjectHeaderRow(group);
822             createObjectSizes(group);
823         }
824     }
825     
826     private void createObjectSizes(Composite parent) {
827         
828         sizeSuppliers = new ArrayList<>(); 
829         try {
830             Pair<Collection<DynamicSizingObject>, Map<String, DynamicSizeMap>> resultSizing = Simantics.getSession().syncRequest(new UniqueRead<Pair<Collection<DynamicSizingObject>, Map<String, DynamicSizeMap>>>() {
831
832                 @Override
833                 public Pair<Collection<DynamicSizingObject>, Map<String, DynamicSizeMap>> perform(ReadGraph graph) throws DatabaseException {
834                     Map<String, DynamicSizeMap> dynamicSizeMaps = DynamicVisualisationsContributions.dynamicSizeMaps(graph);
835                     return Pair.make(DynamicVisualisationsContributions.dynamicSizingObjects(graph), dynamicSizeMaps);
836                 }
837             });
838             
839             for (DynamicSizingObject object : resultSizing.first) {
840                 sizeSuppliers.add(createSizingObjectRow(parent, object, resultSizing.second));
841             }
842         } catch (DatabaseException e) {
843             LOGGER.error("Could not create object sizes", e);
844         }
845     }
846
847     private void initializeSizeBars(Composite parent) {
848         Group group = new Group(parent, SWT.NONE);
849         group.setText("Size Bars");
850         GridDataFactory.fillDefaults().grab(true, false).applyTo(group);
851         GridLayoutFactory.fillDefaults().numColumns(8).margins(5, 5).applyTo(group);
852         
853         createSizeBars(group);
854     }
855
856     private void createSizeBars(Composite parent) {
857         showSizeButton = new Button(parent, SWT.CHECK);
858         showSizeButton.setText("Show");
859         addSelectionListener(showSizeButton);
860         
861         sizeTicksButton = new Button(parent, SWT.CHECK);
862         sizeTicksButton.setText("Ticks");
863         addSelectionListener(sizeTicksButton);
864         
865         sizeGradientButton = new Button(parent, SWT.CHECK);
866         sizeGradientButton.setText("Gradient");
867         addSelectionListener(sizeGradientButton);
868         
869         Label label = new Label(parent, SWT.NONE);
870         label.setText("Location");
871         sizeLocationCombo = new Combo(parent, SWT.READ_ONLY);
872         String[] sizeLocationItems = Stream.of(SizeBarsLocation.values()).map(size -> size.toString()).toArray(String[]::new);
873         sizeLocationCombo.setItems(sizeLocationItems);
874         if (sizeLocationItems.length > 0) {
875             sizeLocationCombo.select(0);
876         }
877         addSelectionListener(sizeLocationCombo);
878         
879         label = new Label(parent, SWT.NONE);
880         label.setText("Size");
881         sizeSizeCombo = new Combo(parent, SWT.READ_ONLY);
882         String[] sizeSizeItems = Stream.of(SizeBarsSize.values()).map(size -> size.toString()).toArray(String[]::new);
883         sizeSizeCombo.setItems(sizeSizeItems);
884         if (sizeSizeItems.length > 0) {
885             sizeSizeCombo.select(0);
886         }
887         addSelectionListener(sizeSizeCombo);
888     }
889
890     private void addSelectionListener(Widget widget) {
891         if (widget instanceof Button) {
892             ((Button) widget).addSelectionListener(new SelectionAdapter() {
893
894                 @Override
895                 public void widgetSelected(SelectionEvent e) {
896                     persistCurrentVisualisationTemplateIfAvailable();
897                 }
898             });
899         } else if (widget instanceof Combo) {
900             ((Combo) widget).addSelectionListener(new SelectionAdapter() {
901
902                 @Override
903                 public void widgetSelected(SelectionEvent e) {
904                     persistCurrentVisualisationTemplateIfAvailable();
905                 }
906             });
907         } else if (widget instanceof Text) {
908             ((Text) widget).addFocusListener(new FocusAdapter() {
909                 
910                 @Override
911                 public void focusLost(FocusEvent e) {
912                     persistCurrentVisualisationTemplateIfAvailable();
913                 }
914             });
915             ((Text) widget).addKeyListener(new KeyAdapter() {
916                 
917                 @Override
918                 public void keyReleased(KeyEvent e) {
919                     if(e.keyCode == SWT.CR || e.keyCode == SWT.LF) {
920                         persistCurrentVisualisationTemplateIfAvailable();
921                     }
922                 }
923             });
924         }
925     }
926
927     public void setParentResource(Resource parentResource) {
928         if (this.parentResource != parentResource) {
929             this.parentResource = parentResource;
930             updateListening();
931         }
932     }
933
934     private void updateListening() {
935         if (visualisationsListener != null)
936             visualisationsListener.dispose();
937         visualisationsListener = new VisualisationsListener(this);
938         Simantics.getSession().asyncRequest(new DynamicVisualisationsRequest(parentResource), visualisationsListener);
939         
940         if (listener != null)
941             listener.dispose();
942         listener = new VisualisationListener(this);
943         Simantics.getSession().asyncRequest(new ActiveDynamicVisualisationsRequest(parentResource), listener);
944     }
945
946     private static class VisualisationsListener implements Listener<Collection<NamedResource>> {
947
948         private static final Logger LOGGER = LoggerFactory.getLogger(VisualisationsListener.class);
949
950         private boolean disposed;
951         private DynamicVisualisationsUI ui;
952         
953         public VisualisationsListener(DynamicVisualisationsUI ui) {
954             this.ui = ui;
955         }
956
957         @Override
958         public void execute(Collection<NamedResource> result) {
959             ui.updateVisualisations(result);
960         }
961
962         @Override
963         public void exception(Throwable t) {
964             LOGGER.error("Could not listen visualisation", t);
965         }
966
967         @Override
968         public boolean isDisposed() {
969             return disposed || ui.isDisposed();
970         }
971
972         public void dispose() {
973             this.disposed = true;
974         }
975     }
976     
977     private static class VisualisationListener implements Listener<DynamicVisualisation> {
978
979         private static final Logger LOGGER = LoggerFactory.getLogger(VisualisationListener.class);
980
981         private boolean disposed;
982         private DynamicVisualisationsUI ui;
983         
984         public VisualisationListener(DynamicVisualisationsUI ui) {
985             this.ui = ui;
986         }
987
988         @Override
989         public void execute(DynamicVisualisation result) {
990             ui.updateVisualisation(result);
991         }
992
993         @Override
994         public void exception(Throwable t) {
995             LOGGER.error("Could not listen visualisation", t);
996         }
997
998         @Override
999         public boolean isDisposed() {
1000             return disposed || ui.isDisposed();
1001         }
1002
1003         public void dispose() {
1004             this.disposed = true;
1005         }
1006     }
1007
1008     public void updateVisualisation(DynamicVisualisation result) {
1009         this.visualisation = result;
1010         Display.getDefault().asyncExec(() -> {
1011             if (getParent().isDisposed())
1012                 return;
1013             
1014             removeVisualisationTemplateButton.setEnabled(visualisation != null && visualisation.getVisualisationResource() != null);
1015             
1016             if (visualisation != null) {
1017                 String[] items = templateSelectionCombo.getItems();
1018                 for (int i = 0; i < items.length; i++) {
1019                     if (visualisation.getName().equals(items[i])) {
1020                         templateSelectionCombo.select(i);
1021                         break;
1022                     }
1023                 }
1024                 
1025                 Map<String, DynamicColorContribution> colorContributions = visualisation.getColorContributions();
1026                 for (Entry<String, DynamicColorContribution> entry : colorContributions.entrySet()) {
1027                     
1028                     ColoringObjectRow coloringObjectRow = coloringRows.get(entry.getKey());
1029                     if (coloringObjectRow != null) {
1030                         
1031                         coloringObjectRow.update(entry.getValue());
1032                         
1033                     } else {
1034                         LOGGER.info("No coloring object visualisation row for key {}", entry.getKey());
1035                     }
1036                 }
1037                 ColorBarOptions colorOptions = visualisation.getColorBarOptions();
1038                 showColorButton.setSelection(colorOptions.isShowColorBars());
1039                 colorTicksButton.setSelection(colorOptions.isShowColorBarsTicks());
1040                 colorGradientButton.setSelection(colorOptions.isUseGradients());
1041                 for (int i = 0; i < colorLocationCombo.getItems().length; i++) {
1042                     String item = colorLocationCombo.getItem(i);
1043                     if (item.equals(colorOptions.getLocation().toString())) {
1044                         colorLocationCombo.select(i);
1045                         break;
1046                     }
1047                 }
1048                 for (int i = 0; i < colorSizeCombo.getItems().length; i++) {
1049                     String item = colorSizeCombo.getItem(i);
1050                     if (item.equals(colorOptions.getSize().toString())) {
1051                         colorSizeCombo.select(i);
1052                         break;
1053                     }
1054                 }
1055                 
1056                 Map<String, DynamicSizeContribution> sizeContributions = visualisation.getSizeContributions();
1057                 for (Entry<String, DynamicSizeContribution> entry : sizeContributions.entrySet()) {
1058                     
1059                     SizingObjectRow sizingObjectRow = sizingRows.get(entry.getKey());
1060                     if (sizingObjectRow != null) {
1061                         
1062                         sizingObjectRow.update(entry.getValue());
1063                         
1064                     } else {
1065                         LOGGER.info("No sizing object visualisation row for key {}", entry.getKey());
1066                     }
1067                 }
1068                 SizeBarOptions sizeOptions = visualisation.getSizeBarOptions();
1069                 showSizeButton.setSelection(sizeOptions.isShowSizeBars());
1070                 sizeTicksButton.setSelection(sizeOptions.isShowSizeBarsTicks());
1071                 sizeGradientButton.setSelection(sizeOptions.isUseGradients());
1072                 for (int i = 0; i < sizeLocationCombo.getItems().length; i++) {
1073                     String item = sizeLocationCombo.getItem(i);
1074                     if (item.equals(sizeOptions.getLocation().toString())) {
1075                         sizeLocationCombo.select(i);
1076                         break;
1077                     }
1078                 }
1079                 for (int i = 0; i < sizeSizeCombo.getItems().length; i++) {
1080                     String item = sizeSizeCombo.getItem(i);
1081                     if (item.equals(sizeOptions.getSize().toString())) {
1082                         sizeSizeCombo.select(i);
1083                         break;
1084                     }
1085                 }
1086             }
1087         });
1088     }
1089
1090     public void updateVisualisations(Collection<NamedResource> result) {
1091         this.visualisations = result;
1092         
1093         Display.getDefault().asyncExec(() -> {
1094             if (getParent().isDisposed())
1095                 return;
1096             templateSelectionCombo.setItems(visualisations.stream().map(NamedResource::getName).collect(Collectors.toList()).toArray(new String[visualisations.size()]));
1097             
1098             if (visualisation != null) {
1099                 String[] items = templateSelectionCombo.getItems();
1100                 for (int i = 0; i < items.length; i++) {
1101                     if (visualisation.getName().equals(items[i])) {
1102                         templateSelectionCombo.select(i);
1103                         break;
1104                     }
1105                 }
1106             }
1107             
1108         });
1109     }
1110 }