001: /*
002: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
003: *
004: * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
005: *
006: * The contents of this file are subject to the terms of either the GNU
007: * General Public License Version 2 only ("GPL") or the Common
008: * Development and Distribution License("CDDL") (collectively, the
009: * "License"). You may not use this file except in compliance with the
010: * License. You can obtain a copy of the License at
011: * http://www.netbeans.org/cddl-gplv2.html
012: * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
013: * specific language governing permissions and limitations under the
014: * License. When distributing the software, include this License Header
015: * Notice in each file and include the License file at
016: * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this
017: * particular file as subject to the "Classpath" exception as provided
018: * by Sun in the GPL Version 2 section of the License file that
019: * accompanied this code. If applicable, add the following below the
020: * License Header, with the fields enclosed by brackets [] replaced by
021: * your own identifying information:
022: * "Portions Copyrighted [year] [name of copyright owner]"
023: *
024: * Contributor(s):
025: *
026: * The Original Software is NetBeans. The Initial Developer of the Original
027: * Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun
028: * Microsystems, Inc. All Rights Reserved.
029: *
030: * If you wish your version of this file to be governed by only the CDDL
031: * or only the GPL Version 2, indicate your decision by adding
032: * "[Contributor] elects to include this software in this distribution
033: * under the [CDDL or GPL Version 2] license." If you do not indicate a
034: * single choice of license, a recipient has the option to distribute
035: * your version of this file under either the CDDL, the GPL Version 2 or
036: * to extend the choice of license to its licensees as provided above.
037: * However, if you add GPL Version 2 code and therefore, elected the GPL
038: * Version 2 license, then the option applies only if the new code is
039: * made subject to such option by the copyright holder.
040: */
041:
042: package org.netbeans.modules.db.explorer.dlg;
043:
044: import java.awt.BorderLayout;
045: import java.io.File;
046: import java.io.IOException;
047: import java.net.MalformedURLException;
048: import java.net.URI;
049: import java.net.URISyntaxException;
050: import java.net.URL;
051: import java.net.URLClassLoader;
052: import java.sql.Driver;
053: import java.util.Enumeration;
054: import java.util.LinkedList;
055: import java.util.List;
056: import java.util.ResourceBundle;
057: import java.util.jar.JarEntry;
058: import java.util.jar.JarFile;
059: import java.util.logging.Level;
060: import java.util.logging.Logger;
061: import javax.swing.DefaultComboBoxModel;
062:
063: import javax.swing.DefaultListModel;
064: import javax.swing.JComponent;
065: import javax.swing.JFileChooser;
066: import javax.swing.ListSelectionModel;
067: import javax.swing.SwingUtilities;
068: import org.openide.filesystems.FileObject;
069:
070: import org.openide.filesystems.FileUtil;
071: import org.openide.filesystems.URLMapper;
072: import org.openide.util.NbBundle;
073: import org.openide.util.RequestProcessor;
074: import org.netbeans.api.db.explorer.JDBCDriver;
075: import org.netbeans.api.progress.ProgressHandle;
076: import org.netbeans.api.progress.ProgressHandleFactory;
077: import org.netbeans.modules.db.util.DriverListUtil;
078: import org.openide.util.Exceptions;
079:
080: public class AddDriverDialog extends javax.swing.JPanel {
081:
082: private DefaultListModel dlm;
083: private List drvs;
084: private boolean customizer;
085: private ProgressHandle progressHandle;
086: private JComponent progressComponent;
087:
088: private static final String BUNDLE = "org.netbeans.modules.db.resources.Bundle"; //NOI18N
089: private static final Logger LOGGER = Logger
090: .getLogger(AddDriverDialog.class.getName());
091:
092: /** Creates new form AddDriverDialog1 */
093: public AddDriverDialog() {
094: customizer = false;
095: initComponents();
096: // hack to force the progressContainerPanel honor its preferred height
097: // without it, the preferred height is sometimes ignored during resize
098: // progressContainerPanel.add(Box.createVerticalStrut(progressContainerPanel.getPreferredSize().height), BorderLayout.EAST);
099: initAccessibility();
100: dlm = (DefaultListModel) drvList.getModel();
101: drvs = new LinkedList();
102: }
103:
104: public AddDriverDialog(JDBCDriver drv) {
105: this ();
106: customizer = true;
107:
108: String fileName = null;
109: URL[] urls = drv.getURLs();
110: for (int i = 0; i < urls.length; i++) {
111: URL url = urls[i];
112: if ("nbinst".equals(url.getProtocol())) { // NOI18N
113: // try to get a file: URL for the nbinst: URL
114: FileObject fo = URLMapper.findFileObject(url);
115: if (fo != null) {
116: URL localURL = URLMapper.findURL(fo,
117: URLMapper.EXTERNAL);
118: if (localURL != null) {
119: url = localURL;
120: }
121: }
122: }
123: FileObject fo = URLMapper.findFileObject(url);
124: if (fo != null) {
125: File diskFile = FileUtil.toFile(fo);
126: if (diskFile != null) {
127: fileName = diskFile.getAbsolutePath();
128: }
129: } else {
130: try {
131: fileName = new File(new URI(url.toExternalForm()))
132: .getAbsolutePath();
133: } catch (URISyntaxException e) {
134: Exceptions.printStackTrace(e);
135: fileName = null;
136: }
137: }
138: if (fileName != null) {
139: dlm.addElement(fileName);
140: // use urls[i], not url, because we want to add the original URL
141: drvs.add(urls[i]);
142: }
143: }
144: drvClassComboBox.addItem(drv.getClassName());
145: drvClassComboBox.setSelectedItem(drv.getClassName());
146: nameTextField.setText(drv.getDisplayName());
147: }
148:
149: private void initAccessibility() {
150: ResourceBundle b = NbBundle.getBundle(BUNDLE);
151: this .getAccessibleContext().setAccessibleDescription(
152: b.getString("ACS_AddDriverDialogA11yDesc")); //NOI18N
153: drvListLabel.getAccessibleContext().setAccessibleDescription(
154: b.getString("ACS_AddDriverDriverFileA11yDesc")); //NOI18N
155: drvList.getAccessibleContext().setAccessibleName(
156: b.getString("ACS_AddDriverDriverFileListA11yName")); //NOI18N
157: drvClassLabel.getAccessibleContext().setAccessibleDescription(
158: b.getString("ACS_AddDriverDriverDriverClassA11yDesc")); //NOI18N
159: drvClassComboBox
160: .getAccessibleContext()
161: .setAccessibleName(
162: b
163: .getString("ACS_AddDriverDriverDriverClassComboBoxA11yName")); //NOI18N
164: nameLabel.getAccessibleContext().setAccessibleDescription(
165: b.getString("ACS_AddDriverDriverNameA11yDesc")); //NOI18N
166: nameTextField
167: .getAccessibleContext()
168: .setAccessibleName(
169: b
170: .getString("ACS_AddDriverDriverNameTextFieldA11yName")); //NOI18N
171: browseButton.getAccessibleContext().setAccessibleDescription(
172: b.getString("ACS_AddDriverAddButtonA11yDesc")); //NOI18N
173: findButton.getAccessibleContext().setAccessibleDescription(
174: b.getString("ACS_AddDriverRemoveButtonA11yDesc")); //NOI18N
175: removeButton.getAccessibleContext().setAccessibleDescription(
176: b.getString("ACS_AddDriverFindButtonA11yDesc")); //NOI18N
177: progressContainerPanel
178: .getAccessibleContext()
179: .setAccessibleName(
180: b.getString("ACS_AddDriverProgressBarA11yName")); //NOI18N
181: progressContainerPanel
182: .getAccessibleContext()
183: .setAccessibleDescription(
184: b.getString("ACS_AddDriverProgressBarA11yDesc")); //NOI18N
185: }
186:
187: /** This method is called from within the constructor to
188: * initialize the form.
189: * WARNING: Do NOT modify this code. The content of this method is
190: * always regenerated by the Form Editor.
191: */
192: // <editor-fold defaultstate="collapsed" desc=" Generated Code ">//GEN-BEGIN:initComponents
193: private void initComponents() {
194: java.awt.GridBagConstraints gridBagConstraints;
195:
196: drvListLabel = new javax.swing.JLabel();
197: drvListScrollPane = new javax.swing.JScrollPane();
198: drvList = new javax.swing.JList();
199: browseButton = new javax.swing.JButton();
200: removeButton = new javax.swing.JButton();
201: drvClassLabel = new javax.swing.JLabel();
202: drvClassComboBox = new javax.swing.JComboBox();
203: findButton = new javax.swing.JButton();
204: nameLabel = new javax.swing.JLabel();
205: nameTextField = new javax.swing.JTextField();
206: progressMessageLabel = new javax.swing.JLabel();
207: progressContainerPanel = new javax.swing.JPanel();
208:
209: FormListener formListener = new FormListener();
210:
211: setLayout(new java.awt.GridBagLayout());
212:
213: drvListLabel.setLabelFor(drvList);
214: org.openide.awt.Mnemonics.setLocalizedText(drvListLabel,
215: NbBundle.getBundle(BUNDLE).getString(
216: "AddDriverDriverFile")); // NOI18N
217: gridBagConstraints = new java.awt.GridBagConstraints();
218: gridBagConstraints.gridx = 0;
219: gridBagConstraints.gridy = 0;
220: gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL;
221: gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTH;
222: gridBagConstraints.insets = new java.awt.Insets(12, 12, 0, 12);
223: add(drvListLabel, gridBagConstraints);
224:
225: drvList.setModel(new DefaultListModel());
226: drvListScrollPane.setViewportView(drvList);
227:
228: gridBagConstraints = new java.awt.GridBagConstraints();
229: gridBagConstraints.gridx = 1;
230: gridBagConstraints.gridy = 0;
231: gridBagConstraints.gridheight = 2;
232: gridBagConstraints.fill = java.awt.GridBagConstraints.BOTH;
233: gridBagConstraints.weightx = 1.0;
234: gridBagConstraints.weighty = 1.0;
235: gridBagConstraints.insets = new java.awt.Insets(11, 0, 0, 11);
236: add(drvListScrollPane, gridBagConstraints);
237:
238: org.openide.awt.Mnemonics.setLocalizedText(browseButton,
239: NbBundle.getBundle(BUNDLE).getString(
240: "AddDriverDriverAdd")); // NOI18N
241: browseButton.addActionListener(formListener);
242: gridBagConstraints = new java.awt.GridBagConstraints();
243: gridBagConstraints.gridx = 2;
244: gridBagConstraints.gridy = 0;
245: gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL;
246: gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTH;
247: gridBagConstraints.insets = new java.awt.Insets(11, 0, 0, 11);
248: add(browseButton, gridBagConstraints);
249:
250: org.openide.awt.Mnemonics.setLocalizedText(removeButton,
251: NbBundle.getBundle(BUNDLE).getString(
252: "AddDriverDriverRemove")); // NOI18N
253: removeButton.addActionListener(formListener);
254: gridBagConstraints = new java.awt.GridBagConstraints();
255: gridBagConstraints.gridx = 2;
256: gridBagConstraints.gridy = 1;
257: gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL;
258: gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTH;
259: gridBagConstraints.insets = new java.awt.Insets(11, 0, 0, 11);
260: add(removeButton, gridBagConstraints);
261:
262: drvClassLabel.setLabelFor(drvClassComboBox);
263: org.openide.awt.Mnemonics.setLocalizedText(drvClassLabel,
264: NbBundle.getBundle(BUNDLE).getString(
265: "AddDriverDriverClass")); // NOI18N
266: gridBagConstraints = new java.awt.GridBagConstraints();
267: gridBagConstraints.gridx = 0;
268: gridBagConstraints.gridy = 2;
269: gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL;
270: gridBagConstraints.insets = new java.awt.Insets(11, 12, 0, 12);
271: add(drvClassLabel, gridBagConstraints);
272:
273: drvClassComboBox.setEditable(true);
274: drvClassComboBox.addActionListener(formListener);
275: gridBagConstraints = new java.awt.GridBagConstraints();
276: gridBagConstraints.gridx = 1;
277: gridBagConstraints.gridy = 2;
278: gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL;
279: gridBagConstraints.insets = new java.awt.Insets(11, 0, 0, 12);
280: add(drvClassComboBox, gridBagConstraints);
281:
282: org.openide.awt.Mnemonics.setLocalizedText(findButton, NbBundle
283: .getBundle(BUNDLE).getString("AddDriverDriverFind")); // NOI18N
284: findButton.addActionListener(formListener);
285: gridBagConstraints = new java.awt.GridBagConstraints();
286: gridBagConstraints.gridx = 2;
287: gridBagConstraints.gridy = 2;
288: gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL;
289: gridBagConstraints.insets = new java.awt.Insets(11, 0, 0, 11);
290: add(findButton, gridBagConstraints);
291:
292: nameLabel.setLabelFor(nameTextField);
293: org.openide.awt.Mnemonics.setLocalizedText(nameLabel, NbBundle
294: .getBundle(BUNDLE).getString("AddDriverDriverName")); // NOI18N
295: gridBagConstraints = new java.awt.GridBagConstraints();
296: gridBagConstraints.gridx = 0;
297: gridBagConstraints.gridy = 3;
298: gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL;
299: gridBagConstraints.insets = new java.awt.Insets(11, 12, 0, 12);
300: add(nameLabel, gridBagConstraints);
301: gridBagConstraints = new java.awt.GridBagConstraints();
302: gridBagConstraints.gridx = 1;
303: gridBagConstraints.gridy = 3;
304: gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL;
305: gridBagConstraints.weightx = 1.0;
306: gridBagConstraints.insets = new java.awt.Insets(11, 0, 0, 12);
307: add(nameTextField, gridBagConstraints);
308:
309: org.openide.awt.Mnemonics.setLocalizedText(
310: progressMessageLabel, " ");
311: gridBagConstraints = new java.awt.GridBagConstraints();
312: gridBagConstraints.gridx = 0;
313: gridBagConstraints.gridy = 4;
314: gridBagConstraints.gridwidth = 3;
315: gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL;
316: gridBagConstraints.weightx = 1.0;
317: gridBagConstraints.insets = new java.awt.Insets(11, 12, 0, 12);
318: add(progressMessageLabel, gridBagConstraints);
319:
320: progressContainerPanel.setMinimumSize(new java.awt.Dimension(
321: 20, 20));
322: progressContainerPanel.setPreferredSize(new java.awt.Dimension(
323: 20, 20));
324: progressContainerPanel.setLayout(new java.awt.BorderLayout());
325: gridBagConstraints = new java.awt.GridBagConstraints();
326: gridBagConstraints.gridx = 0;
327: gridBagConstraints.gridy = 5;
328: gridBagConstraints.gridwidth = 3;
329: gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL;
330: gridBagConstraints.weightx = 1.0;
331: gridBagConstraints.insets = new java.awt.Insets(5, 12, 0, 12);
332: add(progressContainerPanel, gridBagConstraints);
333: }
334:
335: // Code for dispatching events from components to event handlers.
336:
337: private class FormListener implements java.awt.event.ActionListener {
338: FormListener() {
339: }
340:
341: public void actionPerformed(java.awt.event.ActionEvent evt) {
342: if (evt.getSource() == browseButton) {
343: AddDriverDialog.this .browseButtonActionPerformed(evt);
344: } else if (evt.getSource() == removeButton) {
345: AddDriverDialog.this .removeButtonActionPerformed(evt);
346: } else if (evt.getSource() == drvClassComboBox) {
347: AddDriverDialog.this
348: .drvClassComboBoxActionPerformed(evt);
349: } else if (evt.getSource() == findButton) {
350: AddDriverDialog.this .findButtonActionPerformed(evt);
351: }
352: }
353: }// </editor-fold>//GEN-END:initComponents
354:
355: private void drvClassComboBoxActionPerformed(
356: java.awt.event.ActionEvent evt) {//GEN-FIRST:event_drvClassComboBoxActionPerformed
357: if (!customizer)
358: nameTextField.setText(DriverListUtil
359: .findFreeName(DriverListUtil
360: .getName((String) drvClassComboBox
361: .getSelectedItem())));
362: }//GEN-LAST:event_drvClassComboBoxActionPerformed
363:
364: private void removeButtonActionPerformed(
365: java.awt.event.ActionEvent evt) {//GEN-FIRST:event_removeButtonActionPerformed
366: stopProgress();
367:
368: ListSelectionModel lsm = drvList.getSelectionModel();
369: int count = dlm.getSize();
370: int i = 0;
371:
372: if (count < 1)
373: return;
374:
375: do {
376: if (lsm.isSelectedIndex(i)) {
377: dlm.remove(i);
378: drvs.remove(i);
379: count--;
380: continue;
381: }
382: i++;
383: } while (count != i);
384:
385: findDriverClass();
386: }//GEN-LAST:event_removeButtonActionPerformed
387:
388: private void findButtonActionPerformed(
389: java.awt.event.ActionEvent evt) {//GEN-FIRST:event_findButtonActionPerformed
390:
391: RequestProcessor.getDefault().post(new Runnable() {
392: public void run() {
393: startProgress();
394:
395: // This classloader is used to load classes
396: // from the jar files for the driver. We can then use
397: // introspection to see if a class in one of these jar files
398: // implements java.sql.Driver
399: URLClassLoader jarloader = new URLClassLoader(
400: (URL[]) drvs.toArray(new URL[drvs.size()]),
401: this .getClass().getClassLoader());
402:
403: for (int i = 0; i < dlm.size(); i++) {
404: try {
405: String file = (String) dlm.get(i);
406: JarFile jf = new JarFile(file);
407: try {
408: Enumeration entries = jf.entries();
409: while (entries.hasMoreElements()) {
410: JarEntry entry = (JarEntry) entries
411: .nextElement();
412: String className = entry.getName();
413: if (className.endsWith(".class")) { // NOI18N
414: className = className.replace('/',
415: '.');
416: className = className.substring(0,
417: className.length() - 6);
418: if (isDriverClass(jarloader,
419: className)) {
420: addDriverClass(className);
421: }
422: }
423: }
424: } finally {
425: jf.close();
426: }
427: } catch (IOException exc) {
428: //PENDING
429: }
430: }
431:
432: stopProgress();
433: }
434: }, 0);
435: }//GEN-LAST:event_findButtonActionPerformed
436:
437: private void browseButtonActionPerformed(
438: java.awt.event.ActionEvent evt) {//GEN-FIRST:event_browseButtonActionPerformed
439: stopProgress();
440:
441: JFileChooser fc = new JFileChooser();
442: FileUtil.preventFileChooserSymlinkTraversal(fc, null);
443: fc.setDialogTitle(NbBundle.getBundle(BUNDLE).getString(
444: "AddDriver_Chooser_Title")); //NOI18N
445: fc.setMultiSelectionEnabled(true);
446: fc.setAcceptAllFileFilterUsed(false);
447:
448: //.jar and .zip file filter
449: fc.setFileFilter(new javax.swing.filechooser.FileFilter() {
450: public boolean accept(File f) {
451: return (f.isDirectory() || f.getName().endsWith(".jar") || f
452: .getName().endsWith(".zip")); //NOI18N
453: }
454:
455: public String getDescription() {
456: return NbBundle.getBundle(BUNDLE).getString(
457: "AddDriver_Chooser_Filter"); //NOI18N
458: }
459: });
460:
461: if (fc.showOpenDialog(this ) == JFileChooser.APPROVE_OPTION) { //NOI18N
462: File[] files = fc.getSelectedFiles();
463: for (int i = 0; i < files.length; i++)
464: if (files[i] != null && files[i].isFile()) {
465: dlm.addElement(files[i].toString());
466: try {
467: drvs.add(files[i].toURI().toURL());
468: } catch (MalformedURLException exc) {
469: LOGGER.log(Level.WARNING,
470: "Unable to add driver jar file "
471: + files[i].getAbsolutePath()
472: + ": can not convert to URL",
473: exc);
474: }
475: }
476:
477: findDriverClass();
478: }
479: }//GEN-LAST:event_browseButtonActionPerformed
480:
481: // Variables declaration - do not modify//GEN-BEGIN:variables
482: private javax.swing.JButton browseButton;
483: private javax.swing.JComboBox drvClassComboBox;
484: private javax.swing.JLabel drvClassLabel;
485: private javax.swing.JList drvList;
486: private javax.swing.JLabel drvListLabel;
487: private javax.swing.JScrollPane drvListScrollPane;
488: private javax.swing.JButton findButton;
489: private javax.swing.JLabel nameLabel;
490: private javax.swing.JTextField nameTextField;
491: private javax.swing.JPanel progressContainerPanel;
492: private javax.swing.JLabel progressMessageLabel;
493: private javax.swing.JButton removeButton;
494:
495: // End of variables declaration//GEN-END:variables
496:
497: private boolean isDriverClass(URLClassLoader jarloader,
498: String className) {
499: Class clazz;
500:
501: try {
502: clazz = jarloader.loadClass(className);
503: } catch (Throwable t) {
504: LOGGER.log(Level.FINE, null, t);
505: LOGGER.log(Level.INFO,
506: "Got an exception trying to load class "
507: + className
508: + " during search for JDBC drivers in "
509: + " driver jar(s): "
510: + t.getClass().getName() + ": "
511: + t.getMessage()
512: + ". Skipping this class..."); // NOI18N
513:
514: return false;
515: }
516:
517: if (Driver.class.isAssignableFrom(clazz)) {
518: return true;
519: }
520:
521: return false;
522: }
523:
524: public String getDisplayName() {
525: return nameTextField.getText();
526: }
527:
528: public List getDriverLocation() {
529: return drvs;
530: }
531:
532: public String getDriverClass() {
533: return (String) drvClassComboBox.getSelectedItem();
534: }
535:
536: private void findDriverClass() {
537: JarFile jf;
538: String[] drivers = (String[]) DriverListUtil
539: .getDrivers()
540: .toArray(new String[DriverListUtil.getDrivers().size()]);
541:
542: drvClassComboBox.removeAllItems();
543: for (int i = 0; i < drvs.size(); i++) {
544: try {
545: URL url = (URL) drvs.get(i);
546: File file = new File(new URI(url.toExternalForm()));
547: jf = new JarFile(file);
548: for (int j = 0; j < drivers.length; j++)
549: if (jf.getEntry(drivers[j].replace('.', '/')
550: + ".class") != null) //NOI18N
551: addDriverClass(drivers[j]);
552: jf.close();
553: } catch (IOException exc) {
554: //PENDING
555: } catch (URISyntaxException e) {
556: //PENDING
557: }
558: }
559: }
560:
561: private void addDriverClass(String drv) {
562: if (((DefaultComboBoxModel) drvClassComboBox.getModel())
563: .getIndexOf(drv) < 0)
564: drvClassComboBox.addItem(drv);
565: }
566:
567: private void startProgress() {
568: SwingUtilities.invokeLater(new Runnable() {
569: public void run() {
570: progressHandle = ProgressHandleFactory
571: .createHandle(null);
572: progressComponent = ProgressHandleFactory
573: .createProgressComponent(progressHandle);
574: progressContainerPanel.add(progressComponent,
575: BorderLayout.CENTER);
576: progressHandle.start();
577: progressMessageLabel.setText(NbBundle.getBundle(BUNDLE)
578: .getString("AddDriverProgressStart"));
579: }
580: });
581: }
582:
583: private void stopProgress() {
584: SwingUtilities.invokeLater(new Runnable() {
585: public void run() {
586: if (progressHandle != null) {
587: progressHandle.finish();
588: progressHandle = null;
589: progressMessageLabel.setText(" "); // NOI18N
590: progressContainerPanel.remove(progressComponent);
591: // without this, the removed progress component remains painted on its parent... why?
592: repaint();
593: }
594: }
595: });
596: }
597: }
|