Statistics
| Revision:

root / org.gvsig.proj / branches / refactor2018 / org.gvsig.proj / org.gvsig.proj.swing / org.gvsig.proj.swing.impl / src / main / java / org / gvsig / proj / swing / impl / CrsSelectorController.java @ 866

History | View | Annotate | Download (18.5 KB)

1
package org.gvsig.proj.swing.impl;
2

    
3
import java.awt.event.ActionEvent;
4
import java.awt.event.ActionListener;
5
import java.beans.PropertyChangeListener;
6
import java.text.ParseException;
7
import java.util.ArrayList;
8
import java.util.List;
9

    
10
import javax.swing.Action;
11
import javax.swing.Icon;
12
import javax.swing.ImageIcon;
13
import javax.swing.JComponent;
14
import javax.swing.JTree;
15
import javax.swing.event.TreeSelectionEvent;
16
import javax.swing.event.TreeSelectionListener;
17
import javax.swing.tree.DefaultTreeCellRenderer;
18
import javax.swing.tree.DefaultTreeModel;
19
import javax.swing.tree.TreeNode;
20
import javax.swing.tree.TreePath;
21
import javax.swing.tree.TreeSelectionModel;
22

    
23
import org.apache.commons.io.FilenameUtils;
24
import org.gvsig.proj.CoordinateReferenceSystem;
25
import org.gvsig.proj.catalog.CRSDefinition;
26
import org.gvsig.proj.catalog.CRSType;
27
import org.gvsig.proj.catalog.exception.UnsupportedCoordinateReferenceSystemException;
28
import org.gvsig.proj.catalog.extent.GeographicBoundingBox;
29
import org.gvsig.proj.swing.CoordinateReferenceSystemSelectorComponent;
30
import org.gvsig.proj.swing.impl.tree.BranchNode;
31
import org.gvsig.proj.swing.impl.tree.CRSSearchFilter;
32
import org.gvsig.proj.swing.impl.tree.CrsLoader;
33
import org.gvsig.proj.swing.impl.tree.CrsTreeNode;
34
import org.gvsig.proj.swing.impl.tree.DirectGroupLoader;
35
import org.gvsig.proj.swing.impl.tree.GroupLoader;
36
import org.gvsig.proj.swing.impl.tree.AsyncGroupLoader;
37
import org.gvsig.proj.swing.impl.tree.LeafNode;
38
import org.gvsig.proj.swing.impl.tree.LoadedGroup;
39
import org.gvsig.proj.swing.impl.tree.LoadingNode;
40
import org.gvsig.proj.swing.impl.tree.TreeContainer;
41
import org.gvsig.tools.ToolsLocator;
42
import org.gvsig.tools.i18n.I18nManager;
43
import org.gvsig.tools.swing.api.ActionListenerSupport;
44
import org.gvsig.tools.swing.api.Component;
45
import org.gvsig.tools.swing.api.ToolsSwingLocator;
46
import org.gvsig.tools.swing.api.ToolsSwingManager;
47
import org.gvsig.tools.swing.icontheme.IconTheme;
48
import org.slf4j.Logger;
49
import org.slf4j.LoggerFactory;
50

    
51
public class CrsSelectorController extends CrsSelectorView
52
        implements CoordinateReferenceSystemSelectorComponent, 
53
                                                                                                Component,
54
                                                                                                TreeContainer {
55
        private static final long serialVersionUID = -5281166703625751190L;
56
        private CoordinateReferenceSystem selectedCrs = null;
57
        private ActionListenerSupport listenerSupport = ToolsSwingLocator.getToolsSwingManager().createActionListenerSupport();
58
        public static final String CRS_SELECTED_ACTION_COMMAND = "CrsSelected";
59
        protected ImageIcon RECENT_ICON;
60
        protected ImageIcon FAVOURITE_ICON;
61
        private ImageIcon CRS_ICON;
62
        private ImageIcon FOLDER_OPEN_ICON;
63
        private ImageIcon FOLDER_CLOSED_ICON;
64
        private static Logger logger = LoggerFactory.getLogger(CrsSelectorController.class);
65
        protected DefaultTreeModel model;
66
        protected BranchNode root;
67
        protected LoadedGroup recent;
68
        protected LoadedGroup favorite;
69
        protected BranchNode customNode; 
70
        protected IconTheme iconTheme = null;
71
        protected I18nManager i18nManager = null;
72
        final protected DefaultCoordinateReferenceSystemSwingManager manager;
73
        protected String customLabel = "Layer";
74
        protected ArrayList<CRSDefinition> customList = new ArrayList<CRSDefinition>();
75

    
76
        
77
        public CrsSelectorController(DefaultCoordinateReferenceSystemSwingManager manager) {
78
                super();
79
                this.manager = manager;
80
                initIcons();
81
                initComponents();
82
                initTree();
83
                initTreeListeners();
84
                resetSearchCombo(null);
85
                initSearchActions();
86
        }
87
        
88
        @Override
89
        public ImageIcon loadImage( String imageName ) {
90
                return getIconTheme().get(FilenameUtils.getBaseName(imageName));
91
        }
92
        
93
        private IconTheme getIconTheme() {
94
                if (iconTheme == null) {
95
                        iconTheme = ToolsSwingLocator.getIconThemeManager().getCurrent();
96
                }
97
                return iconTheme;
98
        }
99
        
100
        private I18nManager getI18nManager() {
101
                if (i18nManager == null) {
102
                        i18nManager = ToolsLocator.getI18nManager();
103
                }
104
                return i18nManager;
105
        }
106
        
107
        
108
        protected void initComponents() {
109
                IconTheme theme = getIconTheme();
110

    
111
                this.btnSearch.setIcon(theme.get("crs-crsselector-search"));
112
                this.btnSearchRemove.setIcon(theme.get("crs-crsselector-search-remove"));
113
                this.lblFilterAlpha.setIcon(theme.get("crs-crsselector-text-filter"));
114
                this.lblFilterSpatial.setIcon(theme.get("crs-crsselector-spatial-filter"));
115
                this.btnCrsAdd.setIcon(theme.get("crs-crsselector-crs-new"));
116
                this.btnRecentCrsRemove.setIcon(theme.get("crs-crsselector-recent-remove"));
117
                this.btnFavoritesAdd.setIcon(theme.get("crs-crsselector-favorite-add"));
118
                this.btnFavoritesRemove.setIcon(theme.get("crs-crsselector-favorite-remove"));
119
                this.btnFilterDropdown.setIcon(theme.get("crs-crsselector-dropdown"));
120
                
121
                
122
                ToolsSwingManager swingManager = ToolsSwingLocator.getToolsSwingManager();
123
                // hide spatial filter since it is currently not supported
124
                this.btnFilterDropdown.setVisible(false);
125
                this.pnl_filterType.setVisible(false);
126
                this.cboFilterSpatial.setVisible(false);
127
                // hide recent remove since it is currently not supported
128
                this.btnRecentCrsRemove.setVisible(false);
129

    
130
                swingManager.translate(this.btnSearch);
131
                swingManager.translate(this.btnSearchRemove);
132
                swingManager.translate(this.btnCrsAdd);
133
                swingManager.translate(this.btnRecentCrsRemove);
134
                swingManager.translate(this.btnFavoritesAdd);
135
                swingManager.translate(this.btnFavoritesRemove);                
136
                swingManager.translate(this.lblCurrentCrs);
137
                
138
            this.txtDescription.setEditable(false);
139
                this.txtDescription.setText("");
140
                this.txtDescription.setLineWrap(true);
141
                this.txtDescription.setWrapStyleWord(true);
142
                this.txtCrsWkt.setEditable(false);
143
                this.txtCrsWkt.setText("");
144
                
145
                /**
146
                 * TODO Update strings in CrsPanel and change the name of some components
147
                 *
148
                this.btnSearch.setToolTipText(manager.getTranslation("Search"));
149
            this.btnSearchRemove.setToolTipText(manager.getTranslation("Clear search"));
150
            this.btnFavoritesAdd.setToolTipText(manager.getTranslation("Add selected CRS to favorites"));
151
            this.btnFavoritesAdd1.setToolTipText(manager.getTranslation("Remove selected CRS from favorites"));
152
            this.btnCrsAdd.setToolTipText(manager.getTranslation("Create a new CRS"));
153
            this.btnFavoritesAdd2.setToolTipText(manager.getTranslation("Remove selected CRS from recents"));
154
            this.lbl_currentCrs.setText(manager.getTranslation("Current coordinate system:"));
155
            this.jtxt_description.setEditable(false);
156
                this.jtxt_description.setText("");
157
                this.jtxt_description.setLineWrap(true);
158
                this.jtxt_description.setWrapStyleWord(true);
159
                this.txtCrsWkt.setEditable(false);
160
                this.txtCrsWkt.setText("");
161
                */
162
                
163

    
164
                
165
        }
166

    
167
        protected void initTreeListeners() {
168
                this.btnFavoritesAdd.addActionListener(new ActionListener() {
169
                        @Override
170
                        public void actionPerformed(ActionEvent e) {
171
                                CoordinateReferenceSystem selectedCrs = CrsSelectorController.this.getCoordinateReferenceSystem();
172
                                if (selectedCrs != null) {
173
                                        manager.getCoordinateReferenceSystemFavorites().add(selectedCrs.getDefinition());
174
                                        favorite.reloadChildren();
175
                                }
176
                        }
177
                });
178
                
179
                this.btnFavoritesRemove.addActionListener(new ActionListener() {
180
                        @Override
181
                        public void actionPerformed(ActionEvent e) {
182
                                CoordinateReferenceSystem selectedCrs = CrsSelectorController.this.getCoordinateReferenceSystem();
183
                                if (selectedCrs != null) {
184
                                        manager.getCoordinateReferenceSystemFavorites().remove(selectedCrs.getDefinition());
185
                                        favorite.reloadChildren();
186
                                }
187
                        }
188
                });
189
                
190
                /*
191
                this.btnRecentCrsRemove.addActionListener(new ActionListener() {
192
                        @Override
193
                        public void actionPerformed(ActionEvent e) {
194
                                CoordinateReferenceSystem selectedCrs = CrsSelectorController.this.getCoordinateReferenceSystem();
195
                                if (selectedCrs != null) {
196
                                        manager.getCoordinateReferenceSystemHistory().remove(selectedCrs.getDefinition());
197
                                }
198
                        }
199
                });*/
200
        }
201
        
202
        protected void search() {
203
                String filter = getAlphanumericFilter();
204
                if (filter != null && !filter.equals("")) {
205
                        manager.getCoordinateReferenceSystemTextFilterHistory().add(filter);
206
                }
207
                for (int i=0; i<root.getChildCount(); i++) {
208
                        TreeNode node = root.getChildAt(i);
209
                        if (node instanceof BranchNode) {
210
                                ((BranchNode)node).reloadChildren();
211
                        }
212
                }
213
                resetSearchCombo(filter);
214
                model.reload();
215
            expandBranches(this.root);
216
        }
217
        
218
        private void expandBranches(TreeNode root) {
219
                for (int i=0; i<root.getChildCount(); i++) {
220
                        TreeNode node = root.getChildAt(i);
221
                        if (node instanceof BranchNode) {
222
                                TreeNode[] path = this.model.getPathToRoot(node);
223
                                if (path != null) {
224
                                        this.treeResults.expandPath(new TreePath(path));
225
                                }
226
                                expandBranches(node);
227
                        }
228
                }
229
        }
230
        
231
        protected void resetSearchCombo(String selectedItem) {
232
                this.cboFilterAlpha.removeAllItems();
233
                if ("".equals(selectedItem)) {
234
                        this.cboFilterAlpha.insertItemAt(selectedItem, 0);
235
                }
236
                for (String filter: manager.getCoordinateReferenceSystemTextFilterHistory()) {
237
                        this.cboFilterAlpha.addItem(filter);        
238
                }
239
                this.cboFilterAlpha.setSelectedItem(selectedItem);
240
        }
241
        
242
        protected void initSearchActions() {
243
                this.cboFilterAlpha.getActionMap().put("enterPressed", new Action() {
244
                        
245
                        @Override
246
                        public void actionPerformed(ActionEvent e) {
247
                                search();        
248
                        }
249
                        
250
                        @Override
251
                        public void setEnabled(boolean b) {
252
                                // TODO Auto-generated method stub
253
                                
254
                        }
255
                        
256
                        @Override
257
                        public void removePropertyChangeListener(PropertyChangeListener listener) {
258
                                // TODO Auto-generated method stub
259
                                
260
                        }
261
                        
262
                        @Override
263
                        public void putValue(String key, Object value) {
264
                                // TODO Auto-generated method stub
265
                                
266
                        }
267
                        
268
                        @Override
269
                        public boolean isEnabled() {
270
                                return true;
271
                        }
272
                        
273
                        @Override
274
                        public Object getValue(String key) {
275
                                // TODO Auto-generated method stub
276
                                return null;
277
                        }
278
                        
279
                        @Override
280
                        public void addPropertyChangeListener(PropertyChangeListener listener) {
281
                                // TODO Auto-generated method stub
282
                                
283
                        }
284
                });
285
                
286
                this.btnSearch.addActionListener(new ActionListener() {
287
                        
288
                        @Override
289
                        public void actionPerformed(ActionEvent e) {
290
                                search();
291
                                
292
                        }
293
                });
294
                
295
                this.btnSearchRemove.addActionListener(new ActionListener() {
296
                        
297
                        @Override
298
                        public void actionPerformed(ActionEvent e) {
299
                                resetSearchCombo("");
300
                                search();
301
                        }
302
                });
303
                
304
        }
305
        
306
        protected void reloadBranchNodes() {
307
                
308
        }
309
        
310
        protected void initIcons() {
311
                RECENT_ICON = getIconTheme().get("crs-crsselector-recent");
312
                FAVOURITE_ICON = getIconTheme().get("crs-crsselector-favorite");
313
                CRS_ICON = getIconTheme().get("crs-crsselector-crs");
314
                FOLDER_OPEN_ICON = getIconTheme().get("crs-branchnode-tree-group-expanded");
315
                FOLDER_CLOSED_ICON = getIconTheme().get("crs-branchnode-tree-group-collapsed");
316
        }
317
        
318
        protected void initTree() {
319
                root = new BranchNode(null, this, "root");
320
                model = new DefaultTreeModel(root, false);
321
                
322
                JTree tree = this.treeResults;
323
                tree.getSelectionModel().setSelectionMode
324
                (TreeSelectionModel.SINGLE_TREE_SELECTION);
325
                listenerSupport = ToolsSwingLocator.getToolsSwingManager().createActionListenerSupport();
326
                tree.addTreeSelectionListener(new TreeSelectionListener() {
327
                        @Override
328
                        public void valueChanged(TreeSelectionEvent e) {
329
                                CrsSelectorController.this.treeSelectedValueChanged(e);
330
                        }
331
                });
332
                tree.setCellRenderer(new CellRenderer());
333
                
334
                recent = new LoadedGroup(root, this, getI18nManager().getTranslation("Recent"), RECENT_ICON);
335
                recent.setLoader(new RecentLoader(recent));
336
                recent.loadChildren();
337
                root.add(recent);
338
                
339
                favorite = new LoadedGroup(root, this, getI18nManager().getTranslation("Favorites"), FAVOURITE_ICON);
340
                favorite.setLoader(new FavoriteLoader(favorite));
341
                favorite.loadChildren();
342
                root.add(favorite);
343
                
344
                BranchNode epsg = null;
345
                for (String authority: manager.getCatalogManager().getAuthorityNames()) {
346
                        BranchNode authorityNode = new BranchNode(root, this, getI18nManager().getTranslation(authority));
347
                        if (authority.equals("EPSG")) {
348
                                epsg = authorityNode;
349
                        }
350
                        root.add(authorityNode);
351
                        CRSSearchFilter projectedCRSs = new CRSSearchFilter();
352
                        projectedCRSs.setAuthority(authority);
353
                        projectedCRSs.setIncludedTypes(new CRSType[]{CRSType.ProjectedCRSType});
354
                        authorityNode.add(new AuthorityLoadedGroup(authorityNode, projectedCRSs, getI18nManager().getTranslation("Projected")));
355
                        
356
                        CRSSearchFilter geographicCRSs = new CRSSearchFilter();
357
                        geographicCRSs.setAuthority(authority);
358
                        geographicCRSs.setIncludedTypes(new CRSType[]{CRSType.GeographicCRSType});
359
                        authorityNode.add(new AuthorityLoadedGroup(authorityNode, geographicCRSs, getI18nManager().getTranslation("Geographic")));
360

    
361
                        CRSSearchFilter otherCRSs = new CRSSearchFilter();
362
                        otherCRSs.setAuthority(authority);
363
                        otherCRSs.setExcludedTypes(new CRSType[]{CRSType.ProjectedCRSType, CRSType.GeographicCRSType});
364
                        authorityNode.add(new AuthorityLoadedGroup(authorityNode, otherCRSs, getI18nManager().getTranslation("Other")));
365
                }
366
                
367
                initCustomBranch();
368
                
369
                tree.setModel(model);
370
                if (epsg!=null) {
371
                        tree.expandPath(new TreePath(model.getPathToRoot(epsg)));
372
                }
373
        }
374
        
375
        protected void initCustomBranch() {
376
                if (customList.size()>0) {
377
                        if (customNode == null) {
378
                                customNode = new BranchNode(root, this, customLabel);
379
                                root.add(customNode);
380
                        }
381
                        customNode.clear();
382
                        for (CRSDefinition def: customList) {
383
                                CrsTreeNode node = new CrsTreeNode(def, customNode);
384
                                customNode.add(node);
385
                        }
386
                        model.reload();
387
                }
388
        }
389
        
390
        protected void treeSelectedValueChanged(TreeSelectionEvent e) {
391
                TreeNode selection = (TreeNode) this.treeResults.getLastSelectedPathComponent();
392
                if (selection==null) {
393
                        return;
394
                }
395
                if (selection instanceof CrsTreeNode) {
396
                        CRSDefinition def = ((CrsTreeNode)selection).getCRS();
397
                        try {
398
                                selectedCrs = manager.getCRSManager().getCoordinateReferenceSystem(def);
399
                                this.txtDescription.setText(def.getDescription());
400
                                this.txtCrsWkt.setText(def.toWKT());
401
                                listenerSupport.fireActionEvent(new ActionEvent(selection, ActionEvent.ACTION_FIRST, CRS_SELECTED_ACTION_COMMAND));
402
                        } catch (UnsupportedCoordinateReferenceSystemException | ParseException e1) {
403
                                logger.debug("CRS not supported", e);
404
                        }                        
405
                }
406
        }
407

    
408
        @Override
409
        public JComponent asJComponent() {
410
                return this;
411
        }
412

    
413
        @Override
414
        public CoordinateReferenceSystem getCoordinateReferenceSystem() {
415
                return selectedCrs;
416
        }
417

    
418
        @Override
419
        public void setCoordinateReferenceSystem(CoordinateReferenceSystem crs) {
420
                selectedCrs = crs;
421
                // add the provided node to recent nodes and select it
422
                CrsTreeNode node = new CrsTreeNode(crs.getDefinition(), recent);
423
                recent.add(node);
424
                model.reload(recent);
425
                this.treeResults.setSelectionPath(new TreePath(model.getPathToRoot(node)));
426
                
427
        }
428

    
429
        @Override
430
        public void setCustomGroupLabel(String label) {
431
                this.customLabel = label;
432
        }
433

    
434
        @Override
435
        public void addCustomCRS(CRSDefinition customCRS) {
436
                this.customList.add(customCRS);
437
                initCustomBranch();
438
        }
439

    
440
        @Override
441
        public void addCRSSpatialFilter(String label, GeographicBoundingBox boundingBox) {
442
                // TODO Auto-generated method stub
443
        }
444

    
445
        @Override
446
        public void addCRSSelectionListener(ActionListener listener) {
447
                listenerSupport.addActionListener(listener);
448
                
449
        }
450

    
451
        @Override
452
        public void enableSpatialFilter(String label) {
453
                // TODO Auto-generated method stub
454
                
455
        }
456

    
457
        @Override
458
        public void enableAlphanumericFilter(String searchString) {
459
                cboFilterAlpha.addItem(searchString);
460
                cboFilterAlpha.setSelectedItem(searchString);
461
        }
462
        
463
        protected String getAlphanumericFilter() {
464
                return (String) cboFilterAlpha.getSelectedItem();
465
        }
466
        
467
        
468
        public class RecentLoader extends DirectGroupLoader {
469
                public RecentLoader(LoadedGroup caller) {
470
                        super(caller);
471
                }
472
                
473
                @Override
474
                public List<TreeNode> doExecute() {
475
                        String filter;
476
                        if (CrsSelectorController.this.getAlphanumericFilter()!=null) {
477
                                filter = CrsSelectorController.this.getAlphanumericFilter().toLowerCase();
478
                        }
479
                        else {
480
                                filter = null;
481
                        }
482
                        ArrayList<TreeNode> results = new ArrayList<TreeNode>();
483
                        for (CRSDefinition crsDef: manager.getCoordinateReferenceSystemHistory()) {
484
                                if (filter==null || crsDef.toString().toLowerCase().contains(filter)) {
485
                                        results.add(new CrsTreeNode(crsDef, caller));
486
                                }
487
                        }
488
                        return results;
489
                }
490
        }
491
        
492
        public class FavoriteLoader extends DirectGroupLoader {
493
        
494
                public FavoriteLoader(LoadedGroup caller) {
495
                        super(caller);
496
                }
497
                
498
                @Override
499
                public List<TreeNode> doExecute() {
500
                        String filter;
501
                        if (CrsSelectorController.this.getAlphanumericFilter()!=null) {
502
                                filter = CrsSelectorController.this.getAlphanumericFilter().toLowerCase();
503
                        }
504
                        else {
505
                                filter = null;
506
                        }
507
                        ArrayList<TreeNode> results = new ArrayList<TreeNode>();
508
                        for (CRSDefinition crsDef: manager.getCoordinateReferenceSystemFavorites()) {
509
                                if (filter==null || crsDef.toString().toLowerCase().contains(filter)) {
510
                                        results.add(new CrsTreeNode(crsDef, caller));
511
                                }                                
512
                        }
513
                        return results;
514
                }
515
        }
516
        
517
        public class AuthorityLoadedGroup extends LoadedGroup {
518
                protected final CRSSearchFilter filter;
519
                public AuthorityLoadedGroup(TreeNode parent, CRSSearchFilter filter, String message, Icon icon) {
520
                        super(parent, CrsSelectorController.this, message, icon);
521
                        this.filter = filter;
522
                }
523
                
524
                public AuthorityLoadedGroup(TreeNode parent, CRSSearchFilter filter, String message) {
525
                        super(parent, CrsSelectorController.this, message, null);
526
                        this.filter = filter;
527
                }
528

    
529
                protected GroupLoader getLoader() {
530
                        this.filter.setTextFilter(CrsSelectorController.this.getAlphanumericFilter());
531
                        // FIXME: set also spatial filter when supported by API and UI
532
                        this.filter.setSpatialFilter(null);
533
                        return new CrsLoader(filter, this, CrsSelectorController.this.manager.getCatalogManager());
534
                }
535
        }
536

    
537
        public class CellRenderer extends DefaultTreeCellRenderer {
538
                @Override
539
                public java.awt.Component getTreeCellRendererComponent(JTree tree, Object value, boolean sel, boolean expanded,
540
                                boolean leaf, int row, boolean hasFocus) {
541
                        java.awt.Component c = super.getTreeCellRendererComponent(tree, value, selected, expanded, leaf, row, hasFocus);
542
                        if (value instanceof BranchNode) {
543
                                Icon icon = ((BranchNode)value).getIcon(expanded);
544
                                if (icon!=null) {
545
                                        this.setIcon(icon);
546
                                }
547
                                else {
548
                                        if (expanded) {
549
                                                this.setIcon(FOLDER_OPEN_ICON);
550
                                        }
551
                                        else {
552
                                                this.setIcon(FOLDER_CLOSED_ICON);
553
                                        }
554
                                }
555
                        }
556
                        else if (value instanceof CrsTreeNode) {
557
                                this.setIcon(CRS_ICON);
558
                        }
559
                        else if (value instanceof LeafNode) {
560
                                Icon icon = ((LeafNode)value).getIcon();
561
                                if (icon != null) {
562
                                        this.setIcon(icon);
563
                                }
564
                        }
565
                        return this;
566
                }
567

    
568
                
569
        }
570
        
571
        public DefaultCoordinateReferenceSystemSwingManager getManager() {
572
                return manager;
573
        }
574
        
575
        public DefaultTreeModel getModel() {
576
                return this.model;
577
        }
578
        
579
        public LoadingNode createLoadingNode(BranchNode parent) {
580
                return new LoadingNode(parent,
581
                                this,
582
                                getI18nManager().getTranslation("Loading..."));
583
        }
584

    
585

    
586
        @Override
587
        public JTree getTree() {
588
                return this.treeResults;
589
        }
590

    
591

    
592
}