Statistics
| Revision:

svn-gvsig-desktop / tags / v2_0_0_Build_2056 / extensions / extGeoDB / src / org / gvsig / geodb / vectorialdb / wizard / WizardDB.java @ 39006

History | View | Annotate | Download (19 KB)

1
/* gvSIG. Geographic Information System of the Valencian Government
2
*
3
* Copyright (C) 2007-2008 Infrastructures and Transports Department
4
* of the Valencian Government (CIT)
5
*
6
* This program is free software; you can redistribute it and/or
7
* modify it under the terms of the GNU General Public License
8
* as published by the Free Software Foundation; either version 2
9
* of the License, or (at your option) any later version.
10
*
11
* This program is distributed in the hope that it will be useful,
12
* but WITHOUT ANY WARRANTY; without even the implied warranty of
13
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14
* GNU General Public License for more details.
15
*
16
* You should have received a copy of the GNU General Public License
17
* along with this program; if not, write to the Free Software
18
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
19
* MA  02110-1301, USA.
20
*
21
*/
22

    
23
/*
24
* AUTHORS (In addition to CIT):
25
* 2009 IVER T.I   {{Task}}
26
*/
27

    
28
package org.gvsig.geodb.vectorialdb.wizard;
29

    
30
import java.awt.BorderLayout;
31
import java.awt.Window;
32
import java.awt.event.ActionEvent;
33
import java.awt.event.ActionListener;
34
import java.util.ArrayList;
35
import java.util.Iterator;
36
import java.util.List;
37
import java.util.Map;
38
import java.util.Map.Entry;
39

    
40
import javax.swing.DefaultListModel;
41
import javax.swing.ImageIcon;
42
import javax.swing.JComboBox;
43
import javax.swing.JOptionPane;
44
import javax.swing.JPanel;
45
import javax.swing.JScrollPane;
46
import javax.swing.ListSelectionModel;
47
import javax.swing.event.ListSelectionEvent;
48
import javax.swing.event.ListSelectionListener;
49

    
50
import org.apache.commons.collections.map.HashedMap;
51
import org.cresques.cts.IProjection;
52
import org.slf4j.Logger;
53
import org.slf4j.LoggerFactory;
54

    
55
import org.gvsig.andami.IconThemeHelper;
56
import org.gvsig.andami.PluginServices;
57
import org.gvsig.andami.PluginsLocator;
58
import org.gvsig.andami.PluginsManager;
59
import org.gvsig.andami.messages.NotificationManager;
60
import org.gvsig.app.ApplicationLocator;
61
import org.gvsig.app.ApplicationManager;
62
import org.gvsig.app.gui.WizardPanel;
63
import org.gvsig.app.prepareAction.PrepareContext;
64
import org.gvsig.app.project.Project;
65
import org.gvsig.app.project.ProjectManager;
66
import org.gvsig.app.project.documents.table.TableDocument;
67
import org.gvsig.app.project.documents.table.TableManager;
68
import org.gvsig.fmap.dal.DALLocator;
69
import org.gvsig.fmap.dal.DataManager;
70
import org.gvsig.fmap.dal.DataStoreParameters;
71
import org.gvsig.fmap.dal.exception.DataException;
72
import org.gvsig.fmap.dal.exception.InitializeException;
73
import org.gvsig.fmap.dal.exception.ValidateDataParametersException;
74
import org.gvsig.fmap.dal.feature.FeatureStore;
75
import org.gvsig.fmap.dal.serverexplorer.db.DBServerExplorer;
76
import org.gvsig.fmap.dal.serverexplorer.db.DBServerExplorerParameters;
77
import org.gvsig.fmap.dal.store.db.DBStoreParameters;
78
import org.gvsig.fmap.mapcontext.layers.FLayer;
79
import org.gvsig.fmap.mapcontrol.MapControl;
80
import org.gvsig.geodb.ExtDB_Spatial;
81
import org.gvsig.gui.beans.swing.JButton;
82
import org.gvsig.tools.dynobject.DynObject;
83

    
84

    
85
public class WizardDB extends WizardPanel implements ActionListener,
86
                ListSelectionListener {
87

    
88
        /**
89
     * 
90
     */
91
    private static final long serialVersionUID = -7045762275505941695L;
92
    private static final String WIZARD_TAB_NAME = "DB";
93
        private static Logger logger = LoggerFactory.getLogger(WizardDB.class
94
                        .getName());
95

    
96
    private JPanel namePanel = null;
97
        private JPanel tablesPanel = null;
98
        private JScrollPane tablesScrollPane = null;
99
        private AvailableTablesCheckBoxList tablesList = null;
100
        private JComboBox datasourceComboBox = null;
101
        private UserSelectedFieldsPanel fieldsPanel = null;
102
        private UserSelectedFieldsPanel emptyFieldsPanel = null;
103
        private JButton dbButton = null;
104
        private DBServerExplorerParameters dbExplorerParameters;
105

    
106
    private UserTableSettingsPanel settingsPanel = null;
107
        protected UserTableSettingsPanel emptySettingsPanel = null;
108
        private PrepareContext prepareDSContext;
109

    
110
    public WizardDB() {
111
                super();
112
                initialize();
113
        }
114

    
115

    
116
        protected void initialize() {
117
                setTabName(WIZARD_TAB_NAME);
118
                setLayout(null);
119
                setSize(512, 478);
120

    
121

    
122

    
123
                emptyFieldsPanel = new UserSelectedFieldsPanel(null, true, this);
124
                add(emptyFieldsPanel);
125

    
126
        add(getNamePanel(), null);
127
                loadVectorialDBDatasourcesCombo(null);
128

    
129
                add(getTablesPanel(), null);
130

    
131
        emptySettingsPanel = createSettingsPanel(null);
132
        add(emptySettingsPanel);
133

    
134
        }
135

    
136

    
137
    @SuppressWarnings("rawtypes")
138
    private void loadVectorialDBDatasourcesCombo(MyExplorer sel) {
139
        
140
        PluginsManager manager = PluginsLocator.getManager();
141
        DynObject values = manager.getPlugin(ExtDB_Spatial.class).getPluginProperties();
142
        Map connections = (Map) values.getDynValue("db_connections");
143
        if (connections != null){
144
            Iterator it = connections.entrySet().iterator();
145
            getDatasourceComboBox().removeAllItems();
146
            getDatasourceComboBox().addItem("");
147
            while (it.hasNext()){
148
                Map.Entry entry = (Entry) it.next();
149
                MyExplorer myExplorer = new MyExplorer();
150
                myExplorer.setDbExplorerParameters((DBServerExplorerParameters) entry.getValue());
151
                myExplorer.setName((String) entry.getKey());
152
                getDatasourceComboBox().addItem(myExplorer);
153
                if(sel!=null && sel.getName().equalsIgnoreCase(myExplorer.getName())){
154
                    getDatasourceComboBox().setSelectedItem(myExplorer);
155
                }
156
            }
157
        } else {
158
            connections = new HashedMap();
159
            values.setDynValue("db_connections", connections);
160
        }
161
        }
162

    
163
        public void initWizard() {
164
        }
165

    
166
    @Override
167
    public void execute() {
168
        executeWizard();
169
    }
170

    
171
    @Override
172
    public Object executeWizard() {
173
                TablesListItem[] tables = getSelectedTables();
174

    
175
                DataManager man = DALLocator.getDataManager();
176
                FeatureStore store;
177
                
178
                String docName;
179
                TableDocument document;
180
                Project project = ProjectManager.getInstance().getCurrentProject();
181

    
182
                ApplicationManager appGvSIGMan = ApplicationLocator.getManager();
183
                PrepareContext context = this.getPrepareDataStoreContext();
184
                DBStoreParameters storeParams;
185
        List<TableDocument> tabledocs =
186
            new ArrayList<TableDocument>(tables.length);
187
                for (TablesListItem table : tables) {
188
                        storeParams = getParameterForTable(table);
189

    
190
                        try {
191
                                storeParams = (DBStoreParameters) appGvSIGMan
192
                                                .prepareOpenDataStoreParameters(storeParams, context);
193
                        } catch (Exception e2) {
194
                                NotificationManager.addError(e2);
195
                                continue;
196
                        }
197

    
198
                        UserTableSettingsPanel userTableSettingsPanel = table
199
                                        .getUserTableSettingsPanel();
200

    
201
                        docName = userTableSettingsPanel.getUserLayerName();
202
                        try {
203
                                store = (FeatureStore) man.openStore(storeParams.getDataStoreName(), storeParams);
204
                        } catch (Exception e) {
205
                                NotificationManager.addError(e);
206
                return null;
207
                        }
208

    
209
                        try {
210
                                appGvSIGMan.pepareOpenDataSource(
211
                                                store, context);
212
                        } catch (Exception e) {
213
                                NotificationManager.addError(e);
214
                                store.dispose();
215
                return null;
216
                        }
217

    
218
                        document = (TableDocument) ProjectManager.getInstance().createDocument(TableManager.TYPENAME, docName);
219
                        document.setStore(store);
220
            // project.add(document);
221
            tabledocs.add(document);
222
                }
223
        return tabledocs;
224
        }
225

    
226
        protected DBStoreParameters getParameterForTable(TablesListItem table) {
227
                DBStoreParameters parameters = table.getParameters();
228

    
229
                UserTableSettingsPanel userTableSettingsPanel = table
230
                                .getUserTableSettingsPanel();
231

    
232

    
233

    
234
                String fidField = userTableSettingsPanel.getIdFieldName();
235
                //IF is a multiple PK, remove the {} symbols
236
                if (fidField.startsWith("{") && fidField.endsWith("}")) {
237
                        fidField = fidField.substring(1, fidField.length()-1);
238
                }
239
                String[] pkFields = fidField.split(",");              
240
                    
241
                String[] fields = table.getUserSelectedFieldsPanel()
242
                                .getUserSelectedFields(pkFields, null);
243

    
244
                if (userTableSettingsPanel.isSqlActive()) {
245
                        String whereClause = userTableSettingsPanel
246
                                        .getWhereClause();
247
                        parameters.setBaseFilter(whereClause);
248
                } else {
249
                        parameters.setBaseFilter("");
250
                }
251

    
252
                parameters.setFields(fields);
253

    
254

    
255
                return parameters;
256

    
257
        }
258

    
259
        @SuppressWarnings({ "rawtypes", "unchecked" })
260
    protected TablesListItem[] getSelectedTables() {
261
                int count = tablesList.getModel().getSize();
262
                ArrayList resp = new ArrayList();
263

    
264
                for (int i = 0; i < count; i++) {
265
                        TablesListItem item = (TablesListItem) tablesList.getModel()
266
                                        .getElementAt(i);
267

    
268
                        if (item.isSelected()) {
269
                                resp.add(item);
270
                        }
271
                }
272

    
273
                return (TablesListItem[]) resp.toArray(new TablesListItem[0]);
274
        }
275

    
276
        /**
277
         * This method initializes namePanel
278
         *
279
         * @return javax.swing.JPanel
280
         */
281
        private JPanel getNamePanel() {
282
                if (namePanel == null) {
283
                        namePanel = new JPanel();
284
                        namePanel.setLayout(null);
285
                        namePanel.setBounds(new java.awt.Rectangle(5, 5, 501, 51));
286
                        namePanel.setBorder(javax.swing.BorderFactory.createTitledBorder(
287
                                        null, PluginServices.getText(this, "choose_connection"),
288
                                        javax.swing.border.TitledBorder.DEFAULT_JUSTIFICATION,
289
                                        javax.swing.border.TitledBorder.DEFAULT_POSITION, null,
290
                                        null));
291
                        namePanel.add(getDatasourceComboBox(), null);
292
                        namePanel.add(getJdbcButton(), null);
293
                }
294

    
295
                return namePanel;
296
        }
297

    
298
        /**
299
         * This method initializes tablesPanel
300
         *
301
         * @return javax.swing.JPanel
302
         */
303
        private JPanel getTablesPanel() {
304
                if (tablesPanel == null) {
305
                        tablesPanel = new JPanel();
306
                        tablesPanel.setLayout(new BorderLayout());
307
                        tablesPanel.setBorder(javax.swing.BorderFactory.createTitledBorder(
308
                                        null, PluginServices.getText(this, "choose_table"),
309
                                        javax.swing.border.TitledBorder.DEFAULT_JUSTIFICATION,
310
                                        javax.swing.border.TitledBorder.DEFAULT_POSITION, null,
311
                                        null));
312
                        tablesPanel.setBounds(new java.awt.Rectangle(5, 55, 246, 166));
313
                        tablesPanel
314
                                        .add(getTablesScrollPane(), java.awt.BorderLayout.CENTER);
315
                }
316

    
317
                return tablesPanel;
318
        }
319

    
320
        /**
321
         * This method initializes settingsPanel
322
         *
323
         * @return javax.swing.JPanel
324
         */
325

    
326
        /**
327
         * This method initializes tablesScrollPane
328
         *
329
         * @return javax.swing.JScrollPane
330
         */
331
        private JScrollPane getTablesScrollPane() {
332
                if (tablesScrollPane == null) {
333
                        tablesScrollPane = new JScrollPane();
334
                        tablesScrollPane.setViewportView(getTablesList());
335
                }
336

    
337
                return tablesScrollPane;
338
        }
339

    
340
        /**
341
         * This method initializes tablesList
342
         *
343
         * @return javax.swing.JList
344
         */
345
        protected AvailableTablesCheckBoxList getTablesList() {
346
                if (tablesList == null) {
347
                        tablesList = new AvailableTablesCheckBoxList(this);
348
                        tablesList.addListSelectionListener(this);
349
                        tablesList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
350
                }
351

    
352
                return tablesList;
353
        }
354

    
355
        /**
356
         * This method initializes layerNameTextField
357
         *
358
         * @return javax.swing.JTextField
359
         */
360

    
361
        /**
362
         * This method initializes jComboBox
363
         *
364
         * @return javax.swing.JComboBox
365
         */
366
        private JComboBox getDatasourceComboBox() {
367
                if (datasourceComboBox == null) {
368
                        datasourceComboBox = new JComboBox();
369
                        datasourceComboBox
370
                                        .setBounds(new java.awt.Rectangle(10, 20, 446, 21));
371
                        datasourceComboBox.addActionListener(this);
372
                }
373

    
374
                return datasourceComboBox;
375
        }
376

    
377
        public void actionPerformed(ActionEvent arg0) {
378
                if (datasourceComboBox.getItemCount() == 0) {
379
                        setEmptyPanels();
380
                }
381
                Object src = arg0.getSource();
382

    
383
                if (src == datasourceComboBox) {
384
            Object selected = datasourceComboBox.getSelectedItem();
385
            if (selected instanceof MyExplorer) {
386
                MyExplorer sel_obj = (MyExplorer) selected;
387

    
388
                if (sel_obj == null) {
389
                    return;
390
                }
391
                getDatasourceComboBox().repaint();
392
                dbExplorerParameters = sel_obj.getDbSeverExplorerParameters();
393
            }
394

    
395
                } else if (src == dbButton) {
396
                        MyExplorer sel = addNewConnection();
397

    
398
                        if (sel != null) {
399
                                dbExplorerParameters = sel.getDbSeverExplorerParameters();
400
                                loadVectorialDBDatasourcesCombo(sel);
401
                                getDatasourceComboBox().setSelectedItem(sel);
402

    
403
                        }
404
                }
405
                
406
                updateTableList(dbExplorerParameters);
407
        }
408

    
409
        @SuppressWarnings({ "unchecked", "rawtypes" })
410
    private MyExplorer addNewConnection() {
411
                MyExplorer myExplorer = new MyExplorer();
412
                DBServerExplorerParameters resp = null;
413

    
414
                VectorialDBConnectionParamsDialog newco = new VectorialDBConnectionParamsDialog();
415
                newco.showDialog();
416

    
417
                if (newco.isOkPressed()) {
418
                        try {
419
                                resp = newco.getParameters();
420
                        } catch (Exception e) {
421
                                showConnectionErrorMessage(e.getMessage());
422
                                return null;
423
                        }
424
                        PluginsManager manager = PluginsLocator.getManager();
425
            DynObject values = manager.getPlugin(ExtDB_Spatial.class).getPluginProperties();
426
            Map connections = (Map) values.getDynValue("db_connections");
427
            if(connections == null){
428
                connections = new HashedMap();
429
                values.setDynValue("db_connections", connections);
430
            }
431
            connections.put(newco.getConnectionName(), resp);
432
            
433
//                        SingleVectorialDBConnectionExtension.saveAllToPersistence();
434
                        myExplorer.setDbExplorerParameters(resp);
435
                        myExplorer.setName(newco.getConnectionName());
436
                        return myExplorer;
437
                } else {
438
                        return null;
439
                }
440
        }
441

    
442
        protected TablesListItem createTabeListItem(DBServerExplorer dbExplorer,
443
                        DBStoreParameters param) {
444
                return new TablesListItem(dbExplorer, param, this);
445
        }
446

    
447
        /**
448
         * Subclasses of this wizard will return a filtered list
449
         * if necessary
450
         * 
451
         * @param explorer
452
         * @return
453
         * @throws DataException
454
         */
455
        protected List getTableList(DBServerExplorer explorer) throws DataException {
456
            return explorer.list();
457
        }
458
        
459
        @SuppressWarnings("rawtypes")
460
    protected void updateTableList(
461
                        DBServerExplorerParameters dbSeverExplorerParameters2) {
462
                if (dbSeverExplorerParameters2 == null) {
463
                        return;
464
                }
465
                DataManager dm = DALLocator.getDataManager();
466
                DBServerExplorer dbExplorer;
467
                try {
468
                        dbExplorer = (DBServerExplorer) dm.openServerExplorer(dbSeverExplorerParameters2.getExplorerName(), dbSeverExplorerParameters2);
469

    
470
                        List parameters = getTableList(dbExplorer);
471

    
472
                        DefaultListModel lmodel = new DefaultListModel();
473

    
474
                        Iterator iter = parameters.iterator();
475
                        DBStoreParameters param;
476
                        int count = 0;
477
                        while (iter.hasNext()) {
478
                                param = (DBStoreParameters) iter.next();
479
                                lmodel.addElement(createTabeListItem(dbExplorer, param));
480
                                count++;
481
                        }
482

    
483
                        getTablesList().setModel(lmodel);
484
                        getTablesScrollPane().setViewportView(tablesList);
485
                        tablesScrollPane.updateUI();
486
                } catch (InitializeException e) {
487
                        logger.error("While getting table names: " + e.getMessage(), e);
488
                        NotificationManager.showMessageError("While getting table names: "
489
                                        + e.getMessage(), e);
490
                        return;
491
                } catch (DataException e) {
492
                        logger.error("While getting table names: " + e.getMessage(), e);
493
                        NotificationManager.showMessageError("While getting table names: "
494
                                        + e.getMessage(), e);
495
                        return;
496
                } catch (ValidateDataParametersException e) {
497
                        logger.error("While getting table names: " + e.getMessage(), e);
498
                        NotificationManager.showMessageError("While getting table names: "
499
                                        + e.getMessage(), e);
500
                        return;
501
                }
502
        }
503

    
504
        public void valueChanged(ListSelectionEvent arg0) {
505
                Object src = arg0.getSource();
506

    
507
                if (src == tablesList) {
508
                        TablesListItem selected = (TablesListItem) tablesList
509
                                        .getSelectedValue();
510

    
511
                        setSettingsPanels(selected);
512
                        checkFinishable();
513
                }
514
        }
515

    
516
        public boolean areSettingsValid() {
517
                int count = tablesList.getModel().getSize();
518

    
519
                boolean at_least_one = false;
520
                boolean resp = true;
521

    
522
                for (int i = 0; i < count; i++) {
523
                        TablesListItem item = (TablesListItem) tablesList.getModel()
524
                                        .getElementAt(i);
525

    
526
                        if (item.isSelected()) {
527
                                at_least_one = true;
528
                        }
529

    
530
                        if (item.disturbsWizardValidity()) {
531
                                resp = false;
532
                        }
533
                }
534

    
535
                return (at_least_one && resp);
536
        }
537

    
538
        public void checkFinishable() {
539
                boolean finishable = areSettingsValid();
540
                callStateChanged(finishable);
541
        }
542

    
543
        /**
544
         * This method initializes jdbcButton
545
         *
546
         * @return javax.swing.JButton
547
         */
548
        private JButton getJdbcButton() {
549
                if (dbButton == null) {
550
                        dbButton = new JButton();
551
                        dbButton.addActionListener(this);
552
                        dbButton.setToolTipText(PluginServices.getText(this,
553
                                        "add_connection"));
554
                        dbButton.setBounds(new java.awt.Rectangle(465, 20, 26, 21));
555

    
556
                        dbButton.setIcon(IconThemeHelper.getImageIcon("geodb-connection-add"));
557
                }
558

    
559
                return dbButton;
560
        }
561

    
562
        private void showConnectionErrorMessage(String _msg) {
563
                String msg = (_msg.length() > 300) ? "" : (": " + _msg);
564
                String title = PluginServices.getText(this, "connection_error");
565
                JOptionPane.showMessageDialog(this, title + msg, title,
566
                                JOptionPane.ERROR_MESSAGE);
567
        }
568

    
569
        private java.net.URL createResourceUrl(String path) {
570
                return getClass().getClassLoader().getResource(path);
571
        }
572

    
573
        public void setSettingsPanels(TablesListItem actTable) {
574
                if (actTable == null) {
575
                        setEmptyPanels();
576

    
577
                        return;
578
                }
579
                fieldsPanel = actTable.getUserSelectedFieldsPanel();
580

    
581
                removeFieldPanels();
582
                add(fieldsPanel);
583
                fieldsPanel.repaint();
584
                removeSettingsPanels();
585
                add(emptySettingsPanel);
586

    
587
                settingsPanel = createSettingsPanel(actTable);
588

    
589
                removeSettingsPanels();
590
                add(settingsPanel);
591
                settingsPanel.repaint();
592

    
593

    
594
                repaint();
595
        }
596

    
597

    
598

    
599
        protected UserTableSettingsPanel createSettingsPanel(TablesListItem actTable) {
600
                if (actTable == null) {
601
                        return new UserTableSettingsPanel(null, "", true, this, null);
602
                }
603

    
604
                return actTable.getUserTableSettingsPanel();
605
        }
606

    
607
        protected void setEmptyPanels() {
608
                removeFieldPanels();
609
                add(emptyFieldsPanel);
610
                fieldsPanel = emptyFieldsPanel;
611

    
612
                repaint();
613
        }
614

    
615
        private void removeFieldPanels() {
616
                for (int i = 0; i < getComponentCount(); i++) {
617
                        if (getComponent(i) instanceof UserSelectedFieldsPanel) {
618
                                remove(i);
619
                        }
620
                }
621
        }
622

    
623
        public DataStoreParameters[] getParameters() {
624
                try {
625
                        TablesListItem[] selected = getSelectedTables();
626
                        int count = selected.length;
627
                        DBStoreParameters[] dbParameters = new DBStoreParameters[count];
628

    
629
                        for (int i = 0; i < count; i++) {
630
                                TablesListItem item = selected[i];
631

    
632

    
633
                                dbParameters[i] = getParameterForTable(item);
634
                        }
635

    
636
                        return dbParameters;// layerArrayToGroup(all_layers, groupName);
637
                } catch (Exception e) {
638
                        logger.error("While creating jdbc layer: " + e.getMessage(), e);
639
                        NotificationManager.addError("Error al cargar la capa: "
640
                                        + e.getMessage(), e);
641
                }
642

    
643
                return null;
644
        }
645

    
646
        /**
647
         * This method process the errors found in a layer
648
         *
649
         * @param lyr
650
         * @param mapControl
651
         */
652

    
653
        protected void processErrorsOfLayer(FLayer lyr, MapControl mapControl) {
654
                // List errors = lyr.getErrors();
655
                // wp.callError(null);
656
                mapControl.getMapContext().callNewErrorEvent(null);
657
        }
658

    
659
        private void removeSettingsPanels() {
660
                for (int i = 0; i < getComponentCount(); i++) {
661
                        if (getComponent(i) instanceof UserTableSettingsPanel) {
662
                                remove(i);
663
                        }
664
                }
665
        }
666

    
667
        protected PrepareContext getPrepareDataStoreContext() {
668
                if (this.prepareDSContext == null) {
669
                        this.prepareDSContext = new PrepareContext() {
670
                                public Window getOwnerWindow() {
671
                                        return null;
672
                                }
673

    
674
                                public IProjection getViewProjection() {
675
                                        return null;
676
                                }
677

    
678
                        };
679
                }
680
                return this.prepareDSContext;
681
        }
682

    
683
        @Override
684
        public void close() {
685
                // Nothing to do
686
        }
687

    
688
} // @jve:decl-index=0:visual-constraint="10,10"