Java tutorial
/* * JStock - Free Stock Market Software * Copyright (C) 2010 Yan Cheng CHEOK <yccheok@yahoo.com> * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ package org.yccheok.jstock.gui.analysis; import java.awt.Color; import java.awt.Component; import java.io.IOException; import java.io.InputStreamReader; import java.io.Reader; import java.net.URL; import java.nio.charset.Charset; import java.text.MessageFormat; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Observer; import java.util.concurrent.CancellationException; import java.util.concurrent.ExecutionException; import javax.swing.JLabel; import javax.swing.JTable; import javax.swing.SwingWorker; import javax.swing.event.ListSelectionEvent; import javax.swing.event.ListSelectionListener; import javax.swing.table.AbstractTableModel; import javax.swing.table.DefaultTableCellRenderer; import javax.swing.table.TableCellRenderer; import javax.swing.table.TableModel; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.yccheok.jstock.gui.Icons; import org.yccheok.jstock.gui.IndicatorProjectManager; import org.yccheok.jstock.gui.JTableUtilities; import org.yccheok.jstock.gui.Utils; import org.yccheok.jstock.gui.table.GenericRenderer; import org.yccheok.jstock.internationalization.GUIBundle; /** * * @author yccheok */ public class WizardSelectIndicatorJPanel extends javax.swing.JPanel { /** Creates new form WizardSelectIndicatorJPanel */ public WizardSelectIndicatorJPanel(IndicatorProjectManager indicatorProjectManager) { this.indicatorProjectManager = indicatorProjectManager; initComponents(); } /** This method is called from within the constructor to * initialize the form. * WARNING: Do NOT modify this code. The content of this method is * always regenerated by the Form Editor. */ @SuppressWarnings("unchecked") // <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents private void initComponents() { jXHeader1 = new org.jdesktop.swingx.JXHeader(); jPanel1 = new javax.swing.JPanel(); jPanel3 = new javax.swing.JPanel(); jSplitPane1 = new javax.swing.JSplitPane(); jScrollPane1 = new javax.swing.JScrollPane(); jTable1 = new javax.swing.JTable(); jPanel4 = new javax.swing.JPanel(); jPanel5 = new javax.swing.JPanel(); jLabel1 = new javax.swing.JLabel(); jScrollPane2 = new javax.swing.JScrollPane(); jEditorPane1 = new javax.swing.JEditorPane(); jPanel2 = new javax.swing.JPanel(); jLabel3 = new javax.swing.JLabel(); jLabel2 = new javax.swing.JLabel(); setLayout(new java.awt.BorderLayout(10, 10)); java.util.ResourceBundle bundle = java.util.ResourceBundle.getBundle("org/yccheok/jstock/data/gui"); // NOI18N jXHeader1.setDescription(bundle.getString("WizardSelectIndicatorJPanel_Description")); // NOI18N jXHeader1.setIcon(new javax.swing.ImageIcon(getClass().getResource("/images/32x32/ark2.png"))); // NOI18N jXHeader1.setTitle(bundle.getString("WizardSelectIndicatorJPanel_Title")); // NOI18N add(jXHeader1, java.awt.BorderLayout.NORTH); jPanel1.setLayout(new java.awt.BorderLayout(10, 10)); jPanel3.setLayout(new java.awt.BorderLayout(10, 10)); jSplitPane1.setDividerLocation(230); jScrollPane1.setBorder(javax.swing.BorderFactory.createTitledBorder("Indicators")); jScrollPane1.setHorizontalScrollBarPolicy(javax.swing.ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER); jTable1.setAutoCreateRowSorter(true); jTable1.setModel(getEmptyTableModel()); jTable1.setAutoResizeMode(javax.swing.JTable.AUTO_RESIZE_LAST_COLUMN); jTable1.setSelectionMode(javax.swing.ListSelectionModel.SINGLE_SELECTION); this.jTable1.getTableHeader().setReorderingAllowed(false); // When use with AUTO_RESIZE_LAST_COLUMN, must set locking = true. If not, will not work as expected. JTableUtilities.makeTableColumnWidthFit(this.jTable1, 0, 5, true); this.jTable1.setDefaultRenderer(String.class, new IndicatorRenderer()); this.booleanTableCellRenderer = this.jTable1.getDefaultRenderer(Boolean.class); this.jTable1.setDefaultRenderer(Boolean.class, new IndicatorRenderer()); this.jTable1.getSelectionModel().addListSelectionListener(new RowListener()); jScrollPane1.setViewportView(jTable1); jSplitPane1.setLeftComponent(jScrollPane1); jPanel4.setLayout(new java.awt.BorderLayout(10, 10)); jPanel5.setLayout(new java.awt.FlowLayout(java.awt.FlowLayout.LEFT)); jLabel1.setFont(jLabel1.getFont().deriveFont(jLabel1.getFont().getStyle() | java.awt.Font.BOLD, jLabel1.getFont().getSize() + 3)); jLabel1.setText(" "); jPanel5.add(jLabel1); jPanel4.add(jPanel5, java.awt.BorderLayout.NORTH); jScrollPane2.setBorder(javax.swing.BorderFactory.createTitledBorder("Description")); jScrollPane2.setHorizontalScrollBarPolicy(javax.swing.ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER); jEditorPane1.setContentType("text/html"); jEditorPane1.setEditable(false); jEditorPane1.setMinimumSize(new java.awt.Dimension(20, 20)); jEditorPane1.setPreferredSize(new java.awt.Dimension(20, 20)); jEditorPane1.addHyperlinkListener(new javax.swing.event.HyperlinkListener() { public void hyperlinkUpdate(javax.swing.event.HyperlinkEvent evt) { jEditorPane1HyperlinkUpdate(evt); } }); jScrollPane2.setViewportView(jEditorPane1); jPanel4.add(jScrollPane2, java.awt.BorderLayout.CENTER); jSplitPane1.setRightComponent(jPanel4); jPanel3.add(jSplitPane1, java.awt.BorderLayout.CENTER); jPanel1.add(jPanel3, java.awt.BorderLayout.CENTER); jPanel2.setLayout(new java.awt.FlowLayout(java.awt.FlowLayout.LEFT)); jLabel3.setText(bundle.getString("WizardSelectIndicatorJPanel_GettingIndicatorInfo...")); // NOI18N jPanel2.add(jLabel3); jLabel2.setIcon(new javax.swing.ImageIcon(getClass().getResource("/images/16x16/spinner.gif"))); // NOI18N jLabel2.setHorizontalTextPosition(javax.swing.SwingConstants.LEFT); jPanel2.add(jLabel2); jPanel1.add(jPanel2, java.awt.BorderLayout.NORTH); add(jPanel1, java.awt.BorderLayout.CENTER); }// </editor-fold>//GEN-END:initComponents private void jEditorPane1HyperlinkUpdate(javax.swing.event.HyperlinkEvent evt) {//GEN-FIRST:event_jEditorPane1HyperlinkUpdate Utils.launchWebBrowser(evt); }//GEN-LAST:event_jEditorPane1HyperlinkUpdate public void start() { // Only start the download task, if we haven't obtained indicator information. if (this.indicatorDownloadManager == null) { // Cancel any previous task. this.cancel(); this.indicatorDownloadManagerTask = this.getIndicatorDownloadManagerTask(); this.indicatorDownloadManagerTask.execute(); } else { // We cannot solely depend on tableModel, as we click next and back // to this page, some previous "planned install" will already be "installed". // We need to update tableModel to reflect on this. if (this.jTable1.getModel() instanceof IndicatorDownloadManagerTableModel) { ((IndicatorDownloadManagerTableModel) this.jTable1.getModel()).updatePlannedInstall(); } } this.updateGUIState(); } public void cancel() { if (this.indicatorDownloadManagerTask != null) { this.indicatorDownloadManagerTask.cancel(true); } this.indicatorDownloadManagerTask = null; } private TableModel getEmptyTableModel() { return new javax.swing.table.DefaultTableModel(new Object[][] { }, new String[] { GUIBundle.getString("WizardSelectIndicatorJPanel_Install"), GUIBundle.getString("WizardSelectIndicatorJPanel_Indicator") }) { Class[] types = new Class[] { java.lang.Boolean.class, java.lang.String.class }; boolean[] canEdit = new boolean[] { false, false }; @Override public Class getColumnClass(int columnIndex) { return types[columnIndex]; } @Override public boolean isCellEditable(int rowIndex, int columnIndex) { return canEdit[columnIndex]; } }; } private void updateGUIState() { if (this.indicatorDownloadManager != null) { /* Success! */ this.jLabel2.setVisible(false); this.jLabel3.setVisible(true); if (false == this.jTable1.getModel() instanceof IndicatorDownloadManagerTableModel) { // First time. this.jTable1.setModel(new IndicatorDownloadManagerTableModel(this.indicatorDownloadManager, this.indicatorProjectManager)); this.jTable1.setRowSelectionInterval(0, 0); } // IndicatorProjectManager may has been alerted. Refresh table to update install status. this.jTable1.repaint(); final String output = MessageFormat.format( GUIBundle.getString("WizardSelectIndicatorJPanel_NIndicatorWillBeInstalled_template"), this.getPlannedInstallProjectSize()); this.jLabel3.setForeground(Color.BLUE); this.jLabel3.setText(output); JTableUtilities.makeTableColumnWidthFit(jTable1, 0, 5, true); // Inform wizard. this.observable.setChanged(); WizardSelectIndicatorJPanel.this.observable.notifyObservers(); } else { if (this.indicatorDownloadManagerTask != null) { final boolean isIndicatorDownloadManagerTaskDone = this.indicatorDownloadManagerTask.isDone(); if (!isIndicatorDownloadManagerTaskDone) { /* Download In Progress */ this.jLabel2.setVisible(true); this.jLabel3.setVisible(true); this.jLabel3 .setText(GUIBundle.getString("WizardSelectIndicatorJPanel_GettingIndicatorInfo...")); this.jLabel3.setForeground(Color.BLUE); } else { /* Fail! */ this.jLabel2.setVisible(false); this.jLabel3.setVisible(true); this.jLabel3.setText(GUIBundle.getString("WizardSelectIndicatorJPanel_FailToGetIndicatorInfo")); this.jLabel3.setForeground(Color.RED); } } else { /* Not Started Yet */ this.jLabel2.setVisible(true); this.jLabel3.setVisible(true); this.jLabel3.setText(GUIBundle.getString("WizardSelectIndicatorJPanel_GettingIndicatorInfo...")); this.jLabel3.setForeground(Color.BLUE); this.jTable1.setModel(this.getEmptyTableModel()); JTableUtilities.makeTableColumnWidthFit(jTable1, 0, 5, true); } } } private int getTotalProjectSize() { if (this.indicatorDownloadManager == null) { return -1; } return this.indicatorDownloadManager.size(); } private int getInstalledProjectSize() { if (this.indicatorDownloadManager == null) { return -1; } int num = 0; for (int i = 0; i < this.indicatorDownloadManager.size(); i++) { if (this.indicatorProjectManager.contains(this.indicatorDownloadManager.get(i).projectName)) { num++; } } return num; } public List<IndicatorDownloadManager.Info> getPlannedInstallIndicatorDownloadInfos() { if (this.indicatorDownloadManager == null) { // Use emptyList instead of EMPTY_LIST, to avoid compiler warning. return java.util.Collections.emptyList(); } final List<IndicatorDownloadManager.Info> installIndicatorDownloadInfos = new ArrayList<IndicatorDownloadManager.Info>(); final IndicatorDownloadManagerTableModel tableModel = (IndicatorDownloadManagerTableModel) this.jTable1 .getModel(); for (int i = 0; i < this.indicatorDownloadManager.size(); i++) { final IndicatorDownloadManager.Info info = this.indicatorDownloadManager.get(i); if (tableModel.isPlannedInstall(info.projectName)) { installIndicatorDownloadInfos.add(info); } } return installIndicatorDownloadInfos; } private int getPlannedInstallProjectSize() { if (this.indicatorDownloadManager == null) { return -1; } final IndicatorDownloadManagerTableModel tableModel = (IndicatorDownloadManagerTableModel) this.jTable1 .getModel(); int num = 0; for (int i = 0; i < this.indicatorDownloadManager.size(); i++) { final String projectName = this.indicatorDownloadManager.get(i).projectName; if (tableModel.isPlannedInstall(projectName)) { num++; } } return num; } public boolean useFinishButton() { final int total = this.getTotalProjectSize(); final int installed = this.getInstalledProjectSize(); if (total < 0 || installed < 0) { return false; } return (total == installed); } public boolean isNextFinishButtonEnabled() { final int total = this.getTotalProjectSize(); final int installed = this.getInstalledProjectSize(); final int planned = this.getPlannedInstallProjectSize(); if (total < 0 || installed < 0 || planned < 0) { // Not ready yet. return false; } if (total == 0) { // Nothing to install. return true; } if (total == installed) { // All were installed. return true; } return planned > 0; } private SwingWorker<IndicatorDownloadManager, Void> getIndicatorDownloadManagerTask() { SwingWorker<IndicatorDownloadManager, Void> worker = new SwingWorker<IndicatorDownloadManager, Void>() { @Override protected void done() { // The done Method: When you are informed that the SwingWorker // is done via a property change or via the SwingWorker object's // done method, you need to be aware that the get methods can // throw a CancellationException. A CancellationException is a // RuntimeException, which means you do not need to declare it // thrown and you do not need to catch it. Instead, you should // test the SwingWorker using the isCancelled method before you // use the get method. if (this.isCancelled()) { // Cancelled by user explicitly. Do not perform any GUI update. // No pop-up message. WizardSelectIndicatorJPanel.this.indicatorDownloadManager = null; return; } try { WizardSelectIndicatorJPanel.this.indicatorDownloadManager = get(); } catch (InterruptedException ex) { log.error(null, ex); } catch (ExecutionException ex) { log.error(null, ex); } catch (CancellationException ex) { // Some developers suggest to catch this exception, instead of // checking on isCancelled. As I am not confident by merely // isCancelled check can prevent CancellationException (What // if cancellation is happen just after isCancelled check?), // I will apply both techniques. log.error(null, ex); } updateGUIState(); } @Override protected IndicatorDownloadManager doInBackground() { if (isCancelled()) { return null; } final String location = IndicatorDownloadManager.getIndicatorDownloadManagerDescriptionFileLocation( indicatorProjectManager.getPreferredOperatorIndicatorType()); final Utils.InputStreamAndMethod inputStreamAndMethod = Utils .getResponseBodyAsStreamBasedOnProxyAuthOption(location); if (inputStreamAndMethod.inputStream == null) { inputStreamAndMethod.method.releaseConnection(); return null; } // Why we design method parameter to accept Reader instead of // InputStream? // This is because if we construct Reader within fromXML, we will // have to close the Reader within fromXML. This will close the // InputStream implicitly as well. This is not what we want. We // want the caller to close InputStream explicitly. final Reader reader = new InputStreamReader(inputStreamAndMethod.inputStream, Charset.forName("UTF-8")); final IndicatorDownloadManager _indicatorDownloadManager = Utils .fromXML(IndicatorDownloadManager.class, reader); org.yccheok.jstock.file.Utils.close(reader); org.yccheok.jstock.file.Utils.close(inputStreamAndMethod.inputStream); inputStreamAndMethod.method.releaseConnection(); if (_indicatorDownloadManager == null) { return null; } // Perform simple validation. for (int i = 0; i < _indicatorDownloadManager.size(); i++) { if (_indicatorDownloadManager.get(i).type != indicatorProjectManager .getPreferredOperatorIndicatorType()) { return null; } } for (int i = 0; i < _indicatorDownloadManager.size(); i++) { final IndicatorDownloadManager.Info info = _indicatorDownloadManager.get(i); final URL descriptionURL = info.descriptionURL; final String respond = Utils .getResponseBodyAsStringBasedOnProxyAuthOption(descriptionURL.toString()); if (respond != null) { if (respond.contains(Utils.getJStockUUID())) { htmlDescriptionMap.put(info.projectName, respond); } } } return _indicatorDownloadManager; } }; return worker; } private class IndicatorRenderer extends GenericRenderer { /* Help me to obtain JLabel for boolean. */ private final DefaultTableCellRenderer defaultTableCellRenderer = new DefaultTableCellRenderer(); @Override public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) { Component c = null; if (value instanceof Boolean) { final IndicatorDownloadManagerTableModel tableModel = (IndicatorDownloadManagerTableModel) table .getModel(); final String projectName = (String) tableModel.getValueAt(table.convertRowIndexToModel(row), 1); if (tableModel.isInstalled(projectName)) { c = defaultTableCellRenderer.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column); if (c instanceof JLabel) { final JLabel l = (JLabel) c; l.setText(null); l.setHorizontalAlignment(JLabel.CENTER); l.setHorizontalTextPosition(JLabel.CENTER); l.setIcon(Icons.OK); } else { c = booleanTableCellRenderer.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column); } } else { c = booleanTableCellRenderer.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column); } if (isSelected || hasFocus) { return c; } c.setBackground(this.getBackgroundColor(row)); } else { c = super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column); } return c; } } private class RowListener implements ListSelectionListener { @Override public void valueChanged(ListSelectionEvent event) { if (event.getValueIsAdjusting()) { return; } final int view = jTable1.getSelectedRow(); final int index = jTable1.convertRowIndexToModel(view); final String projectName = (String) ((IndicatorDownloadManagerTableModel) jTable1.getModel()) .getValueAt(index, 1); jLabel1.setText(projectName); String htmlDescription = htmlDescriptionMap.get(projectName); jEditorPane1.setText(htmlDescription); // Make the scroll bar scrolls to the top. jEditorPane1.setCaretPosition(0); } } private class IndicatorDownloadManagerTableModel extends AbstractTableModel { private final String[] columnNames = { GUIBundle.getString("WizardSelectIndicatorJPanel_Install"), GUIBundle.getString("WizardSelectIndicatorJPanel_Indicator") }; private final Class[] columnClasses = { Boolean.class, String.class }; private final IndicatorDownloadManager downloadManager; private final IndicatorProjectManager projectManager; private final List<String> planToInstallProjectName = new ArrayList<String>(); public IndicatorDownloadManagerTableModel(IndicatorDownloadManager downloadManager, IndicatorProjectManager projectManager) { this.downloadManager = downloadManager; this.projectManager = projectManager; } public boolean isInstalled(String projectName) { return this.projectManager.contains(projectName); } public boolean isPlannedInstall(String projectName) { return this.planToInstallProjectName.contains(projectName); } public void updatePlannedInstall() { for (int i = 0; i < this.planToInstallProjectName.size(); i++) { if (this.isInstalled(this.planToInstallProjectName.get(i))) { this.planToInstallProjectName.remove(i); i--; } } } @Override public int getRowCount() { return this.downloadManager.size(); } @Override public int getColumnCount() { return columnNames.length; } @Override public Object getValueAt(int rowIndex, int columnIndex) { if (columnIndex == 0) { final String projectName = this.downloadManager.get(rowIndex).projectName; return this.isInstalled(projectName) || this.isPlannedInstall(projectName); } else if (columnIndex == 1) { return this.downloadManager.get(rowIndex).projectName; } return null; } @Override public boolean isCellEditable(int rowIndex, int columnIndex) { if (columnIndex != 0) { return false; } final String projectName = this.downloadManager.get(rowIndex).projectName; return !(this.isInstalled(projectName)); } @Override public void setValueAt(Object aValue, int rowIndex, int columnIndex) { assert (columnIndex == 0); final String projectName = this.downloadManager.get(rowIndex).projectName; /* Hacking way to detect JCheckBox change state at here. */ if ((Boolean) aValue) { if (this.planToInstallProjectName.contains(projectName) == false) { this.planToInstallProjectName.add(projectName); } } else { this.planToInstallProjectName.remove(projectName); } fireTableCellUpdated(rowIndex, columnIndex); WizardSelectIndicatorJPanel.this.updateGUIState(); } @Override public String getColumnName(int col) { return columnNames[col]; } @Override public Class getColumnClass(int c) { return columnClasses[c]; } } public void addObserver(Observer o) { observable.addObserver(o); } private static final Log log = LogFactory.getLog(WizardSelectIndicatorJPanel.class); private final IndicatorProjectManager indicatorProjectManager; private volatile SwingWorker<IndicatorDownloadManager, Void> indicatorDownloadManagerTask = null; private IndicatorDownloadManager indicatorDownloadManager = null; private final Map<String, String> htmlDescriptionMap = new HashMap<String, String>(); /* Hacking way, to obtain JCheckBox for JTable. */ private TableCellRenderer booleanTableCellRenderer = null; // setChanged must be called, before calling notifyObservers. // Cumbersome! Perhaps we still should stick back with our own Subject // Observer framework. private static final class ObservableEx extends java.util.Observable { @Override public void setChanged() { super.setChanged(); } } private final ObservableEx observable = new ObservableEx(); // Variables declaration - do not modify//GEN-BEGIN:variables private javax.swing.JEditorPane jEditorPane1; private javax.swing.JLabel jLabel1; private javax.swing.JLabel jLabel2; private javax.swing.JLabel jLabel3; private javax.swing.JPanel jPanel1; private javax.swing.JPanel jPanel2; private javax.swing.JPanel jPanel3; private javax.swing.JPanel jPanel4; private javax.swing.JPanel jPanel5; private javax.swing.JScrollPane jScrollPane1; private javax.swing.JScrollPane jScrollPane2; private javax.swing.JSplitPane jSplitPane1; private javax.swing.JTable jTable1; private org.jdesktop.swingx.JXHeader jXHeader1; // End of variables declaration//GEN-END:variables }