Statistics
| Revision:

svn-gvsig-desktop / trunk / extensions / extGeoProcessing / src / com / iver / cit / gvsig / geoprocess / core / gui / GeoProcessingOverlayPanel.java @ 10626

History | View | Annotate | Download (18.4 KB)

1
/*
2
 * Created on 04-jul-2005
3
 *
4
 * gvSIG. Sistema de Informaci?n Geogr?fica de la Generalitat Valenciana
5
 *
6
 * Copyright (C) 2004 IVER T.I. and Generalitat Valenciana.
7
 *
8
 * This program is free software; you can redistribute it and/or
9
 * modify it under the terms of the GNU General Public License
10
 * as published by the Free Software Foundation; either version 2
11
 * of the License, or (at your option) any later version.
12
 *
13
 * This program is distributed in the hope that it will be useful,
14
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16
 * GNU General Public License for more details.
17
 *
18
 * You should have received a copy of the GNU General Public License
19
 * along with this program; if not, write to the Free Software
20
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307,USA.
21
 *
22
 * For more information, contact:
23
 *
24
 *  Generalitat Valenciana
25
 *   Conselleria d'Infraestructures i Transport
26
 *   Av. Blasco Ib??ez, 50
27
 *   46010 VALENCIA
28
 *   SPAIN
29
 *
30
 *      +34 963862235
31
 *   gvsig@gva.es
32
 *      www.gvsig.gva.es
33
 *
34
 *    or
35
 *
36
 *   IVER T.I. S.A
37
 *   Salamanca 50
38
 *   46005 Valencia
39
 *   Spain
40
 *
41
 *   +34 963163400
42
 *   dac@iver.es
43
 */
44
package com.iver.cit.gvsig.geoprocess.core.gui;
45

    
46
import java.awt.Component;
47
import java.awt.GridBagConstraints;
48
import java.awt.GridBagLayout;
49
import java.io.File;
50

    
51
import javax.swing.DefaultComboBoxModel;
52
import javax.swing.JButton;
53
import javax.swing.JCheckBox;
54
import javax.swing.JComboBox;
55
import javax.swing.JFileChooser;
56
import javax.swing.JLabel;
57
import javax.swing.JPanel;
58

    
59
import com.hardcode.gdbms.driver.exceptions.ReadDriverException;
60
import com.iver.andami.PluginServices;
61
import com.iver.andami.ui.mdiManager.IWindow;
62
import com.iver.andami.ui.mdiManager.WindowInfo;
63
import com.iver.cit.gvsig.fmap.layers.FBitSet;
64
import com.iver.cit.gvsig.fmap.layers.FLayers;
65
import com.iver.cit.gvsig.fmap.layers.FLyrVect;
66
import com.iver.cit.gvsig.fmap.layers.ReadableVectorial;
67
import com.iver.cit.gvsig.fmap.layers.layerOperations.SingleLayer;
68
import com.iver.utiles.GenericFileFilter;
69
/**
70
 * Panel that allows user to enter clip geoprocess params
71
 * (input layers, etc).
72
 *
73
 * @author jmorell, azabala
74
 */
75
public class GeoProcessingOverlayPanel extends AbstractGeoprocessPanel
76
                                                        implements OverlayPanelIF, IWindow{
77

    
78
        private static final long serialVersionUID = 1L;
79
        private JButton openFileDialogButton = null;
80
        private JCheckBox selectedFirstLayerCheckBox = null;
81
        private JComboBox secondLayerComboBox = null;
82
        private JCheckBox secondLayerSelectedCheckBox = null;
83
        private JLabel jLabel = null;
84
        /**
85
     * Text that describes which type of overlay
86
     * operation will launch this panel
87
     */
88
        private String titleText;
89

    
90
        private WindowInfo viewInfo;
91
        private JPanel resultLayerPanel = null;
92
        private JLabel jLabel7 = null;
93
        private JPanel clipLayerjPanel = null;
94
        private JLabel jLabel5 = null;
95
        private JLabel jLabel6 = null;
96
        private JLabel jLabel4 = null;
97
        private JPanel inputLayerPanel = null;
98
        private JLabel jLabel1 = null;
99
        private JLabel jLabel2 = null;
100
        private JLabel jLabel3 = null;
101

    
102
        /**
103
         * This constructor initializes the set of layers
104
         */
105
        public GeoProcessingOverlayPanel(FLayers layers, String titleText) {
106
                super();
107
                this.layers = layers;
108
                this.titleText = titleText;
109
                initialize();
110
        }
111
        /**
112
         * This method initializes this
113
         *
114
         * @return void
115
         */
116
        private  void initialize() {
117
                jLabel = new JLabel();
118
                this.setLayout(null);
119
                this.setBounds(new java.awt.Rectangle(0,0,486,377));
120
                jLabel.setText(titleText);
121
                //this.add(getFirstLayerComboBox(), null);
122
                jLabel.setBounds(new java.awt.Rectangle(8,20,423,26));
123
                this.add(getInputLayerPanel(), null);
124
                this.add(jLabel, null);
125
                this.add(getResultLayerPanel(), null);
126
                this.add(getClipLayerjPanel(), null);
127
        changeSelectedItemsJCheckBox();
128
        changeSelectedItemsJCheckBox1();
129
        changeSelectedItemsNumberJLabel();
130
        changeSelectedItemsNumberJLabel1();
131
        }
132

    
133
    private void changeSelectedItemsJCheckBox() {
134
        FLyrVect inputSelectable = (FLyrVect)(layers.getLayer((String)layersComboBox.getSelectedItem()));
135
        FBitSet fBitSet = null;
136
                try {
137
                        fBitSet = inputSelectable.getRecordset().getSelection();
138
                } catch (ReadDriverException e) {
139
                        // TODO Auto-generated catch block
140
                        e.printStackTrace();
141
                }
142
        if (fBitSet.cardinality()==0) {
143
            selectedFirstLayerCheckBox.setEnabled(false);
144
            selectedFirstLayerCheckBox.setSelected(false);
145
        } else {
146
            selectedFirstLayerCheckBox.setEnabled(true);
147
            selectedFirstLayerCheckBox.setSelected(true);
148
        }
149
    }
150
    private void changeSelectedItemsJCheckBox1() {
151
        FLyrVect inputSelectable = (FLyrVect)(layers.getLayer((String)secondLayerComboBox.getSelectedItem()));
152
        FBitSet fBitSet = null;
153
                try {
154
                        fBitSet = inputSelectable.getRecordset().getSelection();
155
                } catch (ReadDriverException e) {
156
                        // TODO Auto-generated catch block
157
                        e.printStackTrace();
158
                }
159
        if (fBitSet.cardinality()==0) {
160
            secondLayerSelectedCheckBox.setEnabled(false);
161
            secondLayerSelectedCheckBox.setSelected(false);
162
        } else {
163
            secondLayerSelectedCheckBox.setEnabled(true);
164
            secondLayerSelectedCheckBox.setSelected(true);
165
        }
166
    }
167
    private void changeSelectedItemsNumberJLabel() {
168
        if (getOnlyFirstLayerSelectedCheckBox().isSelected()) {
169
            FLyrVect inputSelectable = (FLyrVect)(layers.getLayer((String)layersComboBox.getSelectedItem()));
170
            FBitSet fBitSet = null;
171
                        try {
172
                                fBitSet = inputSelectable.getRecordset().getSelection();
173
                        } catch (ReadDriverException e) {
174
                                // TODO Auto-generated catch block
175
                                e.printStackTrace();
176
                        }
177
            jLabel3.setText(new Integer(fBitSet.cardinality()).toString());
178
        } else {
179
                ReadableVectorial va = ((SingleLayer)(layers.
180
                                getLayer((String)layersComboBox.
181
                                                getSelectedItem()))).
182
                                                                getSource();
183
            try {
184
                jLabel3.setText(new Integer(va.getShapeCount()).toString());
185
            } catch (ReadDriverException e) {
186
                                // TODO Auto-generated catch block
187
                                e.printStackTrace();
188
                        }
189
        }
190
    }
191
    private void changeSelectedItemsNumberJLabel1() {
192
        if (getOnlySecondLayerSelectedCheckBox().isSelected()) {
193
            FLyrVect inputSelectable = (FLyrVect)(layers.getLayer((String)secondLayerComboBox.getSelectedItem()));
194
            FBitSet fBitSet = null;
195
                        try {
196
                                fBitSet = inputSelectable.getRecordset().getSelection();
197
                        } catch (ReadDriverException e) {
198
                                // TODO Auto-generated catch block
199
                                e.printStackTrace();
200
                        }
201
            jLabel6.setText(new Integer(fBitSet.cardinality()).toString());
202
        } else {
203
                ReadableVectorial va = ((SingleLayer)(layers.getLayer(
204
                                (String)secondLayerComboBox.getSelectedItem())))
205
                                .getSource();
206
            try {
207
                jLabel6.setText(new Integer(va.getShapeCount()).toString());
208
            } catch (ReadDriverException e) {
209
                                // TODO Auto-generated catch block
210
                                e.printStackTrace();
211
                        }
212
        }
213
    }
214

    
215
        /**
216
         *
217
         * TODO
218
         * El panel de seleccion de escritura esta metido a pelo en todas
219
         * las pantallas. Hacer que sea generico, y vinculado a los IWriters
220
         * cargados en el sistema.
221
         *
222
         *
223
         */
224
        private JButton getOpenFileDialogButton() {
225
                if (openFileDialogButton == null) {
226
                        openFileDialogButton = new JButton();
227
                        openFileDialogButton.setText(PluginServices.getText(this,"Abrir"));
228
                        openFileDialogButton.addActionListener(new java.awt.event.ActionListener() {
229
                                public void actionPerformed(java.awt.event.ActionEvent e) {
230
                    openResultFileDialog();
231
                                }
232
                        });
233
                }
234
                return openFileDialogButton;
235
        }
236

    
237
        /**
238
         * This method initializes selectedFirstLayerCheckBox
239
         *
240
         * @return javax.swing.JCheckBox
241
         */
242
        private JCheckBox getOnlyFirstLayerSelectedCheckBox() {
243
                if (selectedFirstLayerCheckBox == null) {
244
                        selectedFirstLayerCheckBox = new JCheckBox();
245
                        selectedFirstLayerCheckBox.setText(PluginServices.getText(this,"Usar_solamente_los_elementos_seleccionados"));
246
                        selectedFirstLayerCheckBox.addItemListener(new java.awt.event.ItemListener() {
247
                                public void itemStateChanged(java.awt.event.ItemEvent e) {
248
                    changeSelectedItemsNumberJLabel();
249
                                }
250
                        });
251
                }
252
                return selectedFirstLayerCheckBox;
253
        }
254
        /**
255
         * This method initializes secondLayerComboBox
256
         *
257
         * @return javax.swing.JComboBox
258
         */
259
        private JComboBox getSecondLayerComboBox() {
260
                if (secondLayerComboBox == null) {
261
                        secondLayerComboBox = new JComboBox();
262
            DefaultComboBoxModel defaultModel = new DefaultComboBoxModel(getLayerNames());
263
            secondLayerComboBox.setModel(defaultModel);
264
                        secondLayerComboBox.addItemListener(new java.awt.event.ItemListener() {
265
                                public void itemStateChanged(java.awt.event.ItemEvent e) {
266
                    changeSelectedItemsJCheckBox1();
267
                    changeSelectedItemsNumberJLabel1();
268
                                }
269
                        });
270
                }
271
                return secondLayerComboBox;
272
        }
273
        /**
274
         * This method initializes secondLayerSelectedCheckBox
275
         *
276
         * @return javax.swing.JCheckBox
277
         */
278
        private JCheckBox getOnlySecondLayerSelectedCheckBox() {
279
                if (secondLayerSelectedCheckBox == null) {
280
                        secondLayerSelectedCheckBox = new JCheckBox();
281
                        secondLayerSelectedCheckBox.setText(PluginServices.getText(this,"Usar_solamente_los_elementos_seleccionados"));
282
                        secondLayerSelectedCheckBox.addItemListener(new java.awt.event.ItemListener() {
283
                                public void itemStateChanged(java.awt.event.ItemEvent e) {
284
                    changeSelectedItemsNumberJLabel1();
285
                                }
286
                        });
287
                }
288
                return secondLayerSelectedCheckBox;
289
        }
290

    
291
        public FLyrVect getSecondLayer() {
292
                String clippingLayer =
293
                        (String) secondLayerComboBox.getSelectedItem();
294
                try {
295
                        return (FLyrVect) layers.getLayer(clippingLayer);
296
                } catch (Exception e) {
297
                        return null;
298
                }
299
        }
300

    
301
        public boolean onlyFirstLayerSelected() {
302
                return selectedFirstLayerCheckBox.isSelected();
303
        }
304

    
305
        public boolean onlySecondLayerSelected() {
306
                return secondLayerSelectedCheckBox.isSelected();
307
        }
308

    
309

    
310
        //TODO Sustituir esto para que pueda trabajar
311
        //con los IWriter que est?n registrados en el sistema
312
        public void openResultFileDialog() {
313
                JFileChooser jfc = new JFileChooser();
314
        jfc.addChoosableFileFilter(
315
                        new GenericFileFilter("shp", "Ficheros SHP"));
316
        if (jfc.showSaveDialog((Component) PluginServices.getMainFrame()) == JFileChooser.APPROVE_OPTION) {
317
            File file = jfc.getSelectedFile();
318
            if (!(file.getPath().endsWith(".shp") || file.getPath().endsWith(".SHP"))){
319
                file = new File(file.getPath()+".shp");
320
            }
321
        }
322
        if (jfc.getSelectedFile()!=null)
323
                getFileNameResultTextField().setText(jfc.getSelectedFile().getAbsolutePath());
324
        }
325

    
326
        //TODO Rehacer esto
327
        public void firstLayerSelectionChecked(boolean checked) {
328
                changeSelectedItemsJCheckBox();
329
        changeSelectedItemsNumberJLabel();
330
        }
331
        public void secondLayerSelectionChecked(boolean checked) {
332
                changeSelectedItemsJCheckBox1();
333
                changeSelectedItemsNumberJLabel1();
334
        }
335

    
336
        public WindowInfo getWindowInfo() {
337
                if (viewInfo == null) {
338
                        viewInfo = new WindowInfo(WindowInfo.MODALDIALOG);
339
                        viewInfo.setTitle(PluginServices.getText(this,
340
                                        "Operacion_de_overlay"));
341
                }
342
                return viewInfo;
343
        }
344
        /**
345
         * This method initializes resultLayerPanel
346
         *
347
         * @return javax.swing.JPanel
348
         */
349
        private JPanel getResultLayerPanel() {
350
                if (resultLayerPanel == null) {
351
                        GridBagConstraints gridBagConstraints2 = new GridBagConstraints();
352
                        gridBagConstraints2.insets = new java.awt.Insets(9,7,7,34);
353
                        gridBagConstraints2.gridy = 0;
354
                        gridBagConstraints2.ipadx = 12;
355
                        gridBagConstraints2.gridx = 2;
356
                        GridBagConstraints gridBagConstraints1 = new GridBagConstraints();
357
                        gridBagConstraints1.fill = java.awt.GridBagConstraints.HORIZONTAL;
358
                        gridBagConstraints1.gridx = 1;
359
                        gridBagConstraints1.gridy = 0;
360
                        gridBagConstraints1.ipadx = 177;
361
                        gridBagConstraints1.ipady = 1;
362
                        gridBagConstraints1.weightx = 1.0;
363
                        gridBagConstraints1.insets = new java.awt.Insets(12,6,9,7);
364
                        GridBagConstraints gridBagConstraints = new GridBagConstraints();
365
                        gridBagConstraints.insets = new java.awt.Insets(14,6,11,6);
366
                        gridBagConstraints.gridy = 0;
367
                        gridBagConstraints.ipadx = 2;
368
                        gridBagConstraints.ipady = 1;
369
                        gridBagConstraints.gridx = 0;
370
                        jLabel7 = new JLabel();
371
                        jLabel7.setText(PluginServices.getText(this, "Cobertura_de_salida") + ":");
372
                        resultLayerPanel = new JPanel();
373
                        resultLayerPanel.setLayout(new GridBagLayout());
374
                        resultLayerPanel.setBorder(javax.swing.BorderFactory.createEtchedBorder(javax.swing.border.EtchedBorder.RAISED));
375
                        resultLayerPanel.add(jLabel7, gridBagConstraints);
376
                        resultLayerPanel.add(getFileNameResultTextField(), gridBagConstraints1);
377
                        resultLayerPanel.add(getOpenFileDialogButton(), gridBagConstraints2);
378
                        resultLayerPanel.setBounds(new java.awt.Rectangle(9,295,445,42));
379
                }
380
                return resultLayerPanel;
381
        }
382
        /**
383
         * This method initializes clipLayerjPanel
384
         *
385
         * @return javax.swing.JPanel
386
         */
387
        private JPanel getClipLayerjPanel() {
388
                if (clipLayerjPanel == null) {
389
                        GridBagConstraints gridBagConstraints12 = new GridBagConstraints();
390
                        gridBagConstraints12.insets = new java.awt.Insets(17,10,5,5);
391
                        gridBagConstraints12.gridy = 0;
392
                        gridBagConstraints12.ipadx = 24;
393
                        gridBagConstraints12.ipady = 7;
394
                        gridBagConstraints12.gridx = 0;
395
                        GridBagConstraints gridBagConstraints11 = new GridBagConstraints();
396
                        gridBagConstraints11.fill = java.awt.GridBagConstraints.HORIZONTAL;
397
                        gridBagConstraints11.gridwidth = 2;
398
                        gridBagConstraints11.gridx = 1;
399
                        gridBagConstraints11.gridy = 0;
400
                        gridBagConstraints11.ipadx = 216;
401
                        gridBagConstraints11.weightx = 1.0;
402
                        gridBagConstraints11.insets = new java.awt.Insets(16,5,4,24);
403
                        GridBagConstraints gridBagConstraints10 = new GridBagConstraints();
404
                        gridBagConstraints10.insets = new java.awt.Insets(4,5,3,104);
405
                        gridBagConstraints10.gridx = 0;
406
                        gridBagConstraints10.gridy = 1;
407
                        gridBagConstraints10.ipadx = 36;
408
                        gridBagConstraints10.gridwidth = 3;
409
                        GridBagConstraints gridBagConstraints9 = new GridBagConstraints();
410
                        gridBagConstraints9.insets = new java.awt.Insets(4,7,19,178);
411
                        gridBagConstraints9.gridy = 2;
412
                        gridBagConstraints9.ipadx = 1;
413
                        gridBagConstraints9.gridx = 2;
414
                        GridBagConstraints gridBagConstraints8 = new GridBagConstraints();
415
                        gridBagConstraints8.insets = new java.awt.Insets(3,11,19,6);
416
                        gridBagConstraints8.gridx = 0;
417
                        gridBagConstraints8.gridy = 2;
418
                        gridBagConstraints8.ipadx = 2;
419
                        gridBagConstraints8.ipady = 1;
420
                        gridBagConstraints8.gridwidth = 2;
421
                        jLabel4 = new JLabel();
422
                        jLabel4.setText(PluginServices.getText(this, "Cobertura_de_recorte") + ":");
423
                        jLabel6 = new JLabel();
424
                        jLabel6.setText("00");
425
                        jLabel5 = new JLabel();
426
                        jLabel5.setText(PluginServices.getText(this, "Numero_de_elementos_seleccionados") + ":");
427
                        clipLayerjPanel = new JPanel();
428
                        clipLayerjPanel.setLayout(new GridBagLayout());
429
                        clipLayerjPanel.setBounds(new java.awt.Rectangle(9,174,445,115));
430
                        clipLayerjPanel.setBorder(javax.swing.BorderFactory.createEtchedBorder(javax.swing.border.EtchedBorder.RAISED));
431
                        clipLayerjPanel.add(jLabel5, gridBagConstraints8);
432
                        clipLayerjPanel.add(jLabel6, gridBagConstraints9);
433
                        clipLayerjPanel.add(getOnlySecondLayerSelectedCheckBox(), gridBagConstraints10);
434
                        clipLayerjPanel.add(getSecondLayerComboBox(), gridBagConstraints11);
435
                        clipLayerjPanel.add(jLabel4, gridBagConstraints12);
436
                }
437
                return clipLayerjPanel;
438
        }
439
        /**
440
         * This method initializes inputLayerPanel
441
         *
442
         * @return javax.swing.JPanel
443
         */
444
        private JPanel getInputLayerPanel() {
445
                if (inputLayerPanel == null) {
446
                        GridBagConstraints gridBagConstraints7 = new GridBagConstraints();
447
                        gridBagConstraints7.fill = java.awt.GridBagConstraints.HORIZONTAL;
448
                        gridBagConstraints7.gridwidth = 2;
449
                        gridBagConstraints7.gridx = 1;
450
                        gridBagConstraints7.gridy = 0;
451
                        gridBagConstraints7.ipadx = 223;
452
                        gridBagConstraints7.weightx = 1.0;
453
                        gridBagConstraints7.insets = new java.awt.Insets(17,7,0,28);
454
                        GridBagConstraints gridBagConstraints6 = new GridBagConstraints();
455
                        gridBagConstraints6.insets = new java.awt.Insets(7,4,7,184);
456
                        gridBagConstraints6.gridy = 2;
457
                        gridBagConstraints6.ipadx = 1;
458
                        gridBagConstraints6.ipady = 9;
459
                        gridBagConstraints6.gridx = 2;
460
                        GridBagConstraints gridBagConstraints5 = new GridBagConstraints();
461
                        gridBagConstraints5.insets = new java.awt.Insets(6,7,9,3);
462
                        gridBagConstraints5.gridx = 0;
463
                        gridBagConstraints5.gridy = 2;
464
                        gridBagConstraints5.ipadx = 7;
465
                        gridBagConstraints5.ipady = 8;
466
                        gridBagConstraints5.gridwidth = 2;
467
                        GridBagConstraints gridBagConstraints4 = new GridBagConstraints();
468
                        gridBagConstraints4.insets = new java.awt.Insets(1,5,5,108);
469
                        gridBagConstraints4.gridx = 0;
470
                        gridBagConstraints4.gridy = 1;
471
                        gridBagConstraints4.ipadx = 33;
472
                        gridBagConstraints4.gridwidth = 3;
473
                        GridBagConstraints gridBagConstraints3 = new GridBagConstraints();
474
                        gridBagConstraints3.insets = new java.awt.Insets(21,12,5,6);
475
                        gridBagConstraints3.gridy = 0;
476
                        gridBagConstraints3.ipadx = 7;
477
                        gridBagConstraints3.gridx = 0;
478
                        jLabel3 = new JLabel();
479
                        jLabel3.setText("00");
480
                        jLabel2 = new JLabel();
481
                        jLabel2.setText(PluginServices.getText(this, "Numero_de_elementos_seleccionados") + ":");
482
                        jLabel1 = new JLabel();
483
                        jLabel1.setText(PluginServices.getText(this, "Cobertura_de_entrada") + ":");
484
                        inputLayerPanel = new JPanel();
485
                        inputLayerPanel.setLayout(new GridBagLayout());
486
                        inputLayerPanel.setBounds(new java.awt.Rectangle(8,56,446,111));
487
                        inputLayerPanel.setBorder(javax.swing.BorderFactory.createEtchedBorder(javax.swing.border.EtchedBorder.RAISED));
488
                        inputLayerPanel.add(jLabel1, gridBagConstraints3);
489
                        inputLayerPanel.add(getOnlyFirstLayerSelectedCheckBox(), gridBagConstraints4);
490
                        inputLayerPanel.add(jLabel2, gridBagConstraints5);
491
                        inputLayerPanel.add(jLabel3, gridBagConstraints6);
492
                        inputLayerPanel.add(getInputLayerComboBox(), gridBagConstraints7);
493
                }
494
                return inputLayerPanel;
495
        }
496

    
497
        /**
498
         * This method initializes inputLayerComboBox
499
         *
500
         * @return javax.swing.JComboBox
501
         */
502
        private JComboBox getInputLayerComboBox() {
503
                if (layersComboBox == null) {
504
                        layersComboBox = new JComboBox();
505
                        DefaultComboBoxModel defaultModel = new DefaultComboBoxModel(getLayerNames());
506
                        layersComboBox.setModel(defaultModel);
507
                        layersComboBox.addItemListener(new java.awt.event.ItemListener() {
508
                                public void itemStateChanged(java.awt.event.ItemEvent e) {
509
                        changeSelectedItemsJCheckBox();
510
                        changeSelectedItemsNumberJLabel();
511
                                }
512
                        });
513
                }
514
                return layersComboBox;
515
        }
516
}  //  @jve:decl-index=0:visual-constraint="57,37"