Mercurial > dive4elements > river
comparison flys-artifacts/src/main/java/de/intevation/flys/artifacts/charts/CrossSectionApp.java @ 3318:dbe2f85bf160
merged flys-artifacts/2.8
author | Thomas Arendsen Hein <thomas@intevation.de> |
---|---|
date | Fri, 28 Sep 2012 12:14:35 +0200 |
parents | ed07dd55f487 |
children | 975f608dd254 |
comparison
equal
deleted
inserted
replaced
2987:98c7a46ec5ae | 3318:dbe2f85bf160 |
---|---|
1 package de.intevation.flys.artifacts.charts; | |
2 | |
3 import de.intevation.flys.backend.SessionFactoryProvider; | |
4 | |
5 import de.intevation.flys.artifacts.geom.Lines; | |
6 | |
7 import de.intevation.flys.model.CrossSection; | |
8 import de.intevation.flys.model.CrossSectionLine; | |
9 import de.intevation.flys.model.CrossSectionPoint; | |
10 | |
11 import de.intevation.flys.utils.Pair; | |
12 | |
13 import de.intevation.flys.jfree.StableXYDifferenceRenderer; | |
14 | |
15 import java.awt.BorderLayout; | |
16 import java.awt.Dimension; | |
17 import java.awt.FlowLayout; | |
18 | |
19 import java.awt.event.ActionEvent; | |
20 import java.awt.event.ActionListener; | |
21 import java.awt.event.ItemEvent; | |
22 import java.awt.event.ItemListener; | |
23 | |
24 import java.awt.geom.Point2D; | |
25 import java.awt.geom.Rectangle2D; | |
26 | |
27 import java.io.File; | |
28 import java.io.FileWriter; | |
29 import java.io.IOException; | |
30 import java.io.PrintWriter; | |
31 | |
32 import java.math.BigDecimal; | |
33 import java.math.MathContext; | |
34 | |
35 import java.util.ArrayList; | |
36 import java.util.Arrays; | |
37 import java.util.List; | |
38 import java.util.Map; | |
39 import java.util.TreeMap; | |
40 | |
41 import java.awt.Color; | |
42 import java.awt.Paint; | |
43 import java.awt.TexturePaint; | |
44 | |
45 import java.awt.image.BufferedImage; | |
46 | |
47 import javax.swing.DefaultComboBoxModel; | |
48 import javax.swing.JButton; | |
49 import javax.swing.JComboBox; | |
50 import javax.swing.JPanel; | |
51 import javax.swing.JScrollPane; | |
52 import javax.swing.JTable; | |
53 import javax.swing.JTextField; | |
54 | |
55 import javax.swing.event.TableModelEvent; | |
56 import javax.swing.event.TableModelListener; | |
57 | |
58 import javax.swing.table.AbstractTableModel; | |
59 | |
60 import org.hibernate.Query; | |
61 import org.hibernate.Session; | |
62 | |
63 import org.jfree.chart.ChartFactory; | |
64 import org.jfree.chart.ChartPanel; | |
65 import org.jfree.chart.ChartUtilities; | |
66 import org.jfree.chart.JFreeChart; | |
67 | |
68 import org.jfree.chart.axis.NumberAxis; | |
69 | |
70 import org.jfree.chart.plot.PlotOrientation; | |
71 import org.jfree.chart.plot.XYPlot; | |
72 | |
73 import org.jfree.data.xy.DefaultXYDataset; | |
74 import org.jfree.data.xy.XYSeries; | |
75 import org.jfree.data.xy.XYDataset; | |
76 import org.jfree.data.xy.XYSeriesCollection; | |
77 import org.jfree.chart.renderer.xy.XYItemRenderer; | |
78 | |
79 import org.jfree.ui.ApplicationFrame; | |
80 import org.jfree.ui.RefineryUtilities; | |
81 | |
82 /** | |
83 * Standalone tech-demo. | |
84 */ | |
85 public class CrossSectionApp | |
86 extends ApplicationFrame | |
87 { | |
88 public static final String RIVER = System.getProperty("river", "Saar"); | |
89 | |
90 public static final String WATER_LEVEL = System.getProperty("waterlevel"); | |
91 | |
92 public static final String KM = System.getProperty("km"); | |
93 | |
94 public static final double EPSILON = 1e-4; | |
95 | |
96 protected Session session; | |
97 | |
98 protected JComboBox crossSectionLinesCB; | |
99 protected JTextField waterlevelTF; | |
100 | |
101 protected ChartPanel chartPanel; | |
102 | |
103 protected Double lastWaterLevel; | |
104 | |
105 protected List<CrossSection> crossSections; | |
106 protected boolean [] drawCrossSection; | |
107 protected boolean [] drawWaterLevel; | |
108 protected boolean [] drawGround; | |
109 protected boolean [] drawFill; | |
110 | |
111 protected Map<Double, List<Pair<CrossSection, CrossSectionLine>>> km2lines; | |
112 | |
113 protected static final Paint TRANSPARENT = createTransparentPaint(); | |
114 | |
115 public class CrossSectionTableModel extends AbstractTableModel { | |
116 | |
117 @Override | |
118 public String getColumnName(int col) { | |
119 switch (col) { | |
120 case 0: return "Peilungsname"; | |
121 case 1: return "Peilung"; | |
122 case 2: return "Wasserstand"; | |
123 case 3: return "Boden"; | |
124 case 4: return "Wasser"; | |
125 } | |
126 return ""; | |
127 } | |
128 | |
129 @Override | |
130 public int getColumnCount() { | |
131 return 5; | |
132 } | |
133 | |
134 @Override | |
135 public int getRowCount() { | |
136 return crossSections != null ? crossSections.size() : 0; | |
137 } | |
138 | |
139 @Override | |
140 public Object getValueAt(int row, int col) { | |
141 if (crossSections == null) return null; | |
142 switch (col) { | |
143 case 0: return crossSections.get(row).getDescription(); | |
144 case 1: return drawCrossSection[row]; | |
145 case 2: return drawWaterLevel[row]; | |
146 case 3: return drawGround[row]; | |
147 case 4: return drawFill[row]; | |
148 } | |
149 return null; | |
150 } | |
151 | |
152 @Override | |
153 public void setValueAt(Object value, int row, int col) { | |
154 switch (col) { | |
155 case 1: | |
156 if (change(drawCrossSection, row, (Boolean)value)) { | |
157 fireTableCellUpdated(row, col); | |
158 } | |
159 break; | |
160 case 2: | |
161 if (change(drawWaterLevel, row, (Boolean)value)) { | |
162 fireTableCellUpdated(row, col); | |
163 } | |
164 break; | |
165 case 3: | |
166 if (change(drawGround, row, (Boolean)value)) { | |
167 fireTableCellUpdated(row, col); | |
168 } | |
169 break; | |
170 case 4: | |
171 if (change(drawFill, row, (Boolean)value)) { | |
172 fireTableCellUpdated(row, col); | |
173 } | |
174 break; | |
175 } | |
176 } | |
177 | |
178 @Override | |
179 public Class<?> getColumnClass(int columnIndex) { | |
180 switch (columnIndex) { | |
181 case 0: return String.class; | |
182 case 1: | |
183 case 2: | |
184 case 3: | |
185 case 4: return Boolean.class; | |
186 } | |
187 return null; | |
188 } | |
189 | |
190 @Override | |
191 public boolean isCellEditable( | |
192 int rowIndex, | |
193 int columnIndex | |
194 ) { | |
195 return columnIndex >= 1 && columnIndex <= 4; | |
196 } | |
197 } // class CrossSectionTableModel | |
198 | |
199 private static boolean change( | |
200 boolean [] values, | |
201 int index, | |
202 boolean value | |
203 ) { | |
204 if (values[index] != value) { | |
205 values[index] = value; | |
206 return true; | |
207 } | |
208 return false; | |
209 } | |
210 | |
211 public static class CrossSectionLineItem { | |
212 | |
213 Double km; | |
214 List<Pair<CrossSection, CrossSectionLine>> lines; | |
215 | |
216 public CrossSectionLineItem( | |
217 Double km, | |
218 List<Pair<CrossSection, CrossSectionLine>> lines | |
219 ) { | |
220 this.km = km; | |
221 this.lines = lines; | |
222 } | |
223 | |
224 public String toString() { | |
225 return String.valueOf(km); | |
226 } | |
227 } // CrossSectionLineItem | |
228 | |
229 public CrossSectionApp(String title) { | |
230 super(title); | |
231 | |
232 session = SessionFactoryProvider | |
233 .createSessionFactory() | |
234 .openSession(); | |
235 | |
236 JPanel content = createContent(); | |
237 content.setPreferredSize(new Dimension(800, 480)); | |
238 setContentPane(content); | |
239 | |
240 } | |
241 | |
242 public List<CrossSection> crossSections(String river) { | |
243 Query query = session.createQuery( | |
244 "from CrossSection where river.name = :river"); | |
245 query.setParameter("river", river); | |
246 return query.list(); | |
247 } | |
248 | |
249 protected Map<Double, List<Pair<CrossSection, CrossSectionLine>>> | |
250 loadAllLines(List<CrossSection> crossSections) { | |
251 Map<Double, List<Pair<CrossSection, CrossSectionLine>>> km2lines = | |
252 new TreeMap<Double, List<Pair<CrossSection, CrossSectionLine>>>(); | |
253 for (CrossSection cs: crossSections) { | |
254 List<CrossSectionLine> lines = cs.getLines(); | |
255 for (CrossSectionLine csl: lines) { | |
256 Double km = Math.round(csl.getKm().doubleValue() * 1000d)/1000d; | |
257 List<Pair<CrossSection, CrossSectionLine>> ls | |
258 = km2lines.get(km); | |
259 if (ls == null) { | |
260 ls = new ArrayList<Pair<CrossSection, CrossSectionLine>>(2); | |
261 km2lines.put(km, ls); | |
262 } | |
263 ls.add(new Pair<CrossSection, CrossSectionLine>(cs, csl)); | |
264 } | |
265 } | |
266 return km2lines; | |
267 } | |
268 | |
269 public JPanel createContent() { | |
270 JPanel panel = new JPanel(new BorderLayout()); | |
271 | |
272 | |
273 JPanel nav = new JPanel(new FlowLayout()); | |
274 | |
275 crossSections = crossSections(RIVER); | |
276 km2lines = loadAllLines(crossSections); | |
277 | |
278 int CS = crossSections.size(); | |
279 Arrays.fill(drawCrossSection = new boolean[CS], true); | |
280 drawWaterLevel = new boolean[CS]; | |
281 drawGround = new boolean[CS]; | |
282 drawFill = new boolean[CS]; | |
283 | |
284 Object [] clis = createCrossSectionLineItems(km2lines); | |
285 | |
286 DefaultComboBoxModel dcbm = new DefaultComboBoxModel(clis); | |
287 | |
288 crossSectionLinesCB = new JComboBox(dcbm); | |
289 | |
290 if (KM != null) { | |
291 try { | |
292 double km = Double.parseDouble(KM); | |
293 | |
294 CrossSectionLineItem found = null; | |
295 | |
296 for (Object o: clis) { | |
297 CrossSectionLineItem csli = (CrossSectionLineItem)o; | |
298 if (Math.abs(csli.km - km) < EPSILON) { | |
299 found = csli; | |
300 break; | |
301 } | |
302 } | |
303 | |
304 if (found != null) { | |
305 crossSectionLinesCB.setSelectedItem(found); | |
306 } | |
307 } | |
308 catch (NumberFormatException nfe) { | |
309 System.err.println("km is not a number: " | |
310 + nfe.getMessage()); | |
311 } | |
312 } | |
313 | |
314 nav.add(crossSectionLinesCB); | |
315 | |
316 crossSectionLinesCB.addItemListener(new ItemListener() { | |
317 @Override | |
318 public void itemStateChanged(ItemEvent ie) { | |
319 if (ie.getStateChange() == ItemEvent.SELECTED) { | |
320 updateChart(); | |
321 } | |
322 } | |
323 }); | |
324 | |
325 waterlevelTF = new JTextField(5); | |
326 | |
327 if (WATER_LEVEL != null) { | |
328 try { | |
329 waterlevelTF.setText( | |
330 (lastWaterLevel = Double.valueOf(WATER_LEVEL)).toString()); | |
331 } | |
332 catch (NumberFormatException nfe) { | |
333 System.err.println("Water level not a number: " + | |
334 nfe.getMessage()); | |
335 } | |
336 } | |
337 | |
338 waterlevelTF.addActionListener(new ActionListener() { | |
339 @Override | |
340 public void actionPerformed(ActionEvent ae) { | |
341 waterLevelChanged(); | |
342 } | |
343 }); | |
344 | |
345 nav.add(waterlevelTF); | |
346 | |
347 JButton dump = new JButton("dump"); | |
348 | |
349 dump.addActionListener(new ActionListener() { | |
350 @Override | |
351 public void actionPerformed(ActionEvent ae) { | |
352 dumpData(); | |
353 } | |
354 }); | |
355 | |
356 nav.add(dump); | |
357 | |
358 | |
359 chartPanel = createChartPanel(); | |
360 | |
361 panel.add(chartPanel, BorderLayout.CENTER); | |
362 | |
363 | |
364 CrossSectionTableModel cstm = new CrossSectionTableModel(); | |
365 | |
366 cstm.addTableModelListener(new TableModelListener() { | |
367 @Override | |
368 public void tableChanged(TableModelEvent e) { | |
369 updateChart(); | |
370 } | |
371 }); | |
372 | |
373 JTable crossTable = new JTable(cstm); | |
374 | |
375 JPanel west = new JPanel(new BorderLayout()); | |
376 JScrollPane scrollPane = new JScrollPane(crossTable); | |
377 west.add(scrollPane); | |
378 | |
379 west.add(nav, BorderLayout.SOUTH); | |
380 | |
381 panel.add(west, BorderLayout.WEST); | |
382 | |
383 return panel; | |
384 } | |
385 | |
386 protected void waterLevelChanged() { | |
387 String value = waterlevelTF.getText(); | |
388 try { | |
389 lastWaterLevel = Double.valueOf(value); | |
390 } | |
391 catch (NumberFormatException nfe) { | |
392 waterlevelTF.setText( | |
393 lastWaterLevel != null ? lastWaterLevel.toString() : ""); | |
394 return; | |
395 } | |
396 updateChart(); | |
397 } | |
398 | |
399 protected void updateChart() { | |
400 | |
401 CrossSectionLineItem csli = | |
402 (CrossSectionLineItem)crossSectionLinesCB.getSelectedItem(); | |
403 | |
404 JFreeChart chart = createChart(); | |
405 | |
406 chartPanel.setChart(chart); | |
407 } | |
408 | |
409 protected ChartPanel createChartPanel() { | |
410 CrossSectionLineItem csli = | |
411 (CrossSectionLineItem)crossSectionLinesCB.getSelectedItem(); | |
412 | |
413 JFreeChart chart = createChart(); | |
414 | |
415 return new ChartPanel(chart); | |
416 } | |
417 | |
418 protected void dumpData() { | |
419 | |
420 CrossSectionLineItem csli = | |
421 (CrossSectionLineItem)crossSectionLinesCB.getSelectedItem(); | |
422 | |
423 if (csli == null) { | |
424 return; | |
425 } | |
426 | |
427 | |
428 double km = Math.round(csli.km.doubleValue() * 1000d)/1000d; | |
429 | |
430 String kmS = String.valueOf(km).replace(".", "-"); | |
431 | |
432 int i = 1; | |
433 File file = new File("cross-section-" + kmS + ".txt"); | |
434 while (file.exists()) { | |
435 file = new File("cross-section-" + kmS + "[" + (i++) + "].txt"); | |
436 } | |
437 | |
438 System.err.println("dump points to file '" + file + "'"); | |
439 | |
440 PrintWriter out = null; | |
441 | |
442 MathContext mc = new MathContext(3); | |
443 | |
444 try { | |
445 out = | |
446 new PrintWriter( | |
447 new FileWriter(file)); | |
448 | |
449 for (Pair<CrossSection, CrossSectionLine> pair: csli.lines) { | |
450 out.println("# " + pair.getA().getDescription()); | |
451 for (CrossSectionPoint point: pair.getB().getPoints()) { | |
452 out.println( | |
453 new BigDecimal(point.getX()).round(mc) + " " + | |
454 new BigDecimal(point.getY()).round(mc)); | |
455 } | |
456 } | |
457 | |
458 out.flush(); | |
459 } | |
460 catch (IOException ioe) { | |
461 ioe.printStackTrace(); | |
462 } | |
463 finally { | |
464 if (out != null) { | |
465 out.close(); | |
466 } | |
467 } | |
468 } | |
469 | |
470 public void generateWaterLevels( | |
471 List<Point2D> points, | |
472 List<Pair<XYDataset, XYItemRenderer>> datasets | |
473 ) { | |
474 if (points == null || points.isEmpty() || lastWaterLevel == null) { | |
475 return; | |
476 } | |
477 | |
478 double [][] data = Lines.createWaterLines(points, lastWaterLevel).points; | |
479 XYSeries series = | |
480 new XYSeries(String.valueOf(lastWaterLevel), false); | |
481 | |
482 double [] x = data[0]; | |
483 double [] y = data[1]; | |
484 for (int i = 0; i < x.length; ++i) { | |
485 series.add(x[i], y[i], false); | |
486 } | |
487 | |
488 datasets.add(new Pair<XYDataset, XYItemRenderer>( | |
489 new XYSeriesCollection(series), null)); | |
490 } | |
491 | |
492 public void generateFill( | |
493 List<Point2D> points, | |
494 String legend, | |
495 List<Pair<XYDataset, XYItemRenderer>> datasets | |
496 ) { | |
497 if (points == null || points.isEmpty() || lastWaterLevel == null) { | |
498 return; | |
499 } | |
500 | |
501 double [][] data = Lines.createWaterLines(points, lastWaterLevel).points; | |
502 double [][] values = CrossSectionLine.fetchCrossSectionProfile(points); | |
503 | |
504 DefaultXYDataset dataset = new DefaultXYDataset(); | |
505 | |
506 dataset.addSeries(legend + "-Linie", values); | |
507 dataset.addSeries(legend + "-Fl\u00e4che", data); | |
508 | |
509 datasets.add(new Pair<XYDataset, XYItemRenderer>( | |
510 dataset, | |
511 new StableXYDifferenceRenderer( | |
512 TRANSPARENT, Color.blue, false))); | |
513 } | |
514 | |
515 public void generateProfile( | |
516 List<Point2D> points, | |
517 String legend, | |
518 List<Pair<XYDataset, XYItemRenderer>> datasets | |
519 ) { | |
520 if (points == null || points.isEmpty()) { | |
521 return; | |
522 } | |
523 | |
524 double [][] values = CrossSectionLine.fetchCrossSectionProfile(points); | |
525 | |
526 XYSeries series = new XYSeries(legend, false); | |
527 | |
528 double [] x = values[0]; | |
529 double [] y = values[1]; | |
530 for (int i = 0; i < x.length; ++i) { | |
531 series.add(x[i], y[i], false); | |
532 } | |
533 | |
534 datasets.add(new Pair<XYDataset, XYItemRenderer>( | |
535 new XYSeriesCollection(series), null));; | |
536 } | |
537 | |
538 | |
539 /** | |
540 * @param legend the legend entry. | |
541 */ | |
542 public void generateGround( | |
543 List<Point2D> points, | |
544 String legend, | |
545 List<Pair<XYDataset, XYItemRenderer>> datasets | |
546 ) { | |
547 if (points == null || points.isEmpty()) { | |
548 return; | |
549 } | |
550 | |
551 double [][] values = CrossSectionLine.fetchCrossSectionProfile(points); | |
552 | |
553 DefaultXYDataset dataset = new DefaultXYDataset(); | |
554 | |
555 XYSeries series = new XYSeries(legend, false); | |
556 | |
557 dataset.addSeries(legend, values); | |
558 | |
559 StableXYDifferenceRenderer renderer = | |
560 new StableXYDifferenceRenderer(); | |
561 | |
562 datasets.add(new Pair<XYDataset, XYItemRenderer>( | |
563 dataset, renderer)); | |
564 } | |
565 | |
566 public List<Pair<XYDataset, XYItemRenderer>> generateDatasets() { | |
567 | |
568 List<Pair<XYDataset, XYItemRenderer>> datasets = | |
569 new ArrayList<Pair<XYDataset, XYItemRenderer>>(); | |
570 | |
571 CrossSectionLineItem csli = | |
572 (CrossSectionLineItem)crossSectionLinesCB.getSelectedItem(); | |
573 | |
574 for (int i = 0; i < drawCrossSection.length; ++i) { | |
575 List<Point2D> points = null; | |
576 CrossSection cs = crossSections.get(i); | |
577 | |
578 if (drawGround[i]) { | |
579 for (Pair<CrossSection, CrossSectionLine> csl: csli.lines) { | |
580 if (csl.getA() == cs) { | |
581 if (points == null) { | |
582 points = csl.getB().fetchCrossSectionLinesPoints(); | |
583 } | |
584 generateGround( | |
585 points, | |
586 cs.getDescription() + "/Boden", | |
587 datasets); | |
588 break; | |
589 } | |
590 } | |
591 } | |
592 | |
593 if (drawFill[i]) { | |
594 for (Pair<CrossSection, CrossSectionLine> csl: csli.lines) { | |
595 if (csl.getA() == cs) { | |
596 if (points == null) { | |
597 points = csl.getB().fetchCrossSectionLinesPoints(); | |
598 } | |
599 | |
600 generateFill( | |
601 points, cs.getDescription(), datasets); | |
602 break; | |
603 } | |
604 } | |
605 } | |
606 | |
607 if (drawCrossSection[i]) { | |
608 for (Pair<CrossSection, CrossSectionLine> csl: csli.lines) { | |
609 if (csl.getA() == cs) { | |
610 if (points == null) { | |
611 points = csl.getB().fetchCrossSectionLinesPoints(); | |
612 } | |
613 | |
614 generateProfile( | |
615 points, cs.getDescription(), datasets); | |
616 break; | |
617 } | |
618 } | |
619 } | |
620 | |
621 if (drawWaterLevel[i]) { | |
622 for (Pair<CrossSection, CrossSectionLine> csl: csli.lines) { | |
623 if (csl.getA() == cs) { | |
624 if (points == null) { | |
625 points = csl.getB().fetchCrossSectionLinesPoints(); | |
626 } | |
627 generateWaterLevels(points, datasets); | |
628 break; | |
629 } | |
630 } | |
631 } | |
632 | |
633 } | |
634 | |
635 return datasets; | |
636 } | |
637 | |
638 protected Object [] createCrossSectionLineItems( | |
639 Map<Double, List<Pair<CrossSection, CrossSectionLine>>> km2lines | |
640 ) { | |
641 Object [] result = new Object[km2lines.size()]; | |
642 int i = 0; | |
643 for (Map.Entry<Double, List<Pair<CrossSection, CrossSectionLine>>> entry: | |
644 km2lines.entrySet()) { | |
645 result[i++] = new CrossSectionLineItem( | |
646 entry.getKey(), | |
647 entry.getValue()); | |
648 } | |
649 return result; | |
650 } | |
651 | |
652 | |
653 public JFreeChart createChart() { | |
654 JFreeChart chart = ChartFactory.createXYLineChart( | |
655 null, | |
656 "Abstand [m]", | |
657 "H\u00f6he [m]", | |
658 null, | |
659 PlotOrientation.VERTICAL, | |
660 true, | |
661 true, | |
662 false); | |
663 | |
664 List<Pair<XYDataset, XYItemRenderer>> datasets = | |
665 generateDatasets(); | |
666 | |
667 XYPlot plot = chart.getXYPlot(); | |
668 | |
669 for (int i = 0, N = datasets.size(); i < N; ++i) { | |
670 Pair<XYDataset, XYItemRenderer> p = datasets.get(i); | |
671 plot.setDataset(i, p.getA()); | |
672 plot.mapDatasetToRangeAxis(i, 0); | |
673 XYItemRenderer renderer = p.getB(); | |
674 if (renderer != null) { | |
675 plot.setRenderer(i, renderer); | |
676 } | |
677 } | |
678 | |
679 NumberAxis yAxis = (NumberAxis)plot.getRangeAxis(); | |
680 yAxis.setAutoRangeIncludesZero(false); | |
681 | |
682 ChartUtilities.applyCurrentTheme(chart); | |
683 return chart; | |
684 } | |
685 | |
686 protected static Paint createTransparentPaint() { | |
687 BufferedImage texture = new BufferedImage( | |
688 1, 1, BufferedImage.TYPE_4BYTE_ABGR); | |
689 | |
690 return new TexturePaint( | |
691 texture, new Rectangle2D.Double(0d, 0d, 0d, 0d)); | |
692 } | |
693 | |
694 public static void main(String [] args) { | |
695 CrossSectionApp csa = new CrossSectionApp("Querprofile"); | |
696 csa.pack(); | |
697 RefineryUtilities.centerFrameOnScreen(csa); | |
698 csa.setVisible(true); | |
699 } | |
700 } | |
701 // vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 : |