/*
* Copyright 2007-2008 Sun Microsystems, Inc. All Rights Reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* - Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* - Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* - Neither the name of Sun Microsystems nor the names of its
* contributors may be used to endorse or promote products derived
* from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
* IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package org.jdesktop.swingx.demos.table;
import static org.jdesktop.beansbinding.AutoBinding.UpdateStrategy.READ;
import java.awt.BorderLayout;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Insets;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Logger;
import javax.swing.Box;
import javax.swing.JCheckBox;
import javax.swing.JComponent;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JProgressBar;
import javax.swing.JScrollPane;
import javax.swing.JTextField;
import javax.swing.SwingWorker;
import javax.swing.UIManager;
import javax.swing.SwingWorker.StateValue;
import javax.swing.border.CompoundBorder;
import javax.swing.border.EmptyBorder;
import javax.swing.border.TitledBorder;
import javax.swing.event.TableModelEvent;
import javax.swing.event.TableModelListener;
import javax.swing.table.JTableHeader;
import org.jdesktop.beansbinding.BeanProperty;
import org.jdesktop.beansbinding.BindingGroup;
import org.jdesktop.beansbinding.Bindings;
import org.jdesktop.swingx.JXFrame;
import org.jdesktop.swingx.JXStatusBar;
import org.jdesktop.swingx.JXTable;
import org.jdesktop.swingx.JXTableHeader;
import org.jdesktop.swingx.decorator.HighlighterFactory;
import org.jdesktop.swingxset.util.DemoUtils;
import com.sun.swingset3.DemoProperties;
import com.sun.swingset3.demos.Stacker;
/**
* A demo for the {@code JXTable}. This demo displays the same functionality as
* {@link com.sun.swingset3.demos.table.TableDemo}, using SwingX components and methodologies.
* <p>
* It is not possible to extend {@code TableDemo}, since the display components are private. This
* class replicates contents and behavior in that class and may fall out of sync.
*
* @author Karl George Schaefer
* @author Jeanette Winzenberg (Devoxx '08 version)
* @author aim (original TableDemo)
*/
@DemoProperties(
value = "JXTable Demo",
category = "Data",
description = "Demonstrates JXTable, an enhanced data grid display.",
sourceFiles = {
"org/jdesktop/swingx/demos/table/XTableDemo.java",
"org/jdesktop/swingx/demos/table/OscarRendering.java",
"org/jdesktop/swingx/demos/table/CustomColumnFactory.java",
"org/jdesktop/swingx/demos/table/OscarFiltering.java",
"org/jdesktop/swingx/demos/table/resources/XTableDemo.properties"
}
)
public class XTableDemo extends JPanel {
static final Logger logger = Logger.getLogger(XTableDemo.class.getName());
private OscarTableModel oscarModel;
private JPanel controlPanel;
private Stacker dataPanel;
private JXTable oscarTable;
private JCheckBox winnersCheckbox;
private JTextField filterField;
private JComponent statusBarLeft;
private JLabel actionStatus;
private JLabel tableStatus;
private JLabel tableRows;
private JProgressBar progressBar;
private OscarFiltering filterController;
public XTableDemo() {
initComponents();
configureDisplayProperties();
DemoUtils.injectResources(this);
bind();
}
/**
* Customizes display properties of contained components.
* This is data-unrelated.
*/
private void configureDisplayProperties() {
//<snip> JXTable display properties
// show column control
oscarTable.setColumnControlVisible(true);
// replace grid lines with striping
oscarTable.setShowGrid(false, false);
oscarTable.addHighlighter(HighlighterFactory.createSimpleStriping());
// initialize preferred size for table's viewable area
oscarTable.setVisibleRowCount(10);
// </snip>
//<snip> JXTable column properties
// create and configure a custom column factory
CustomColumnFactory factory = new CustomColumnFactory();
OscarRendering.configureColumnFactory(factory, getClass());
// set the factory before setting the table model
oscarTable.setColumnFactory(factory);
// </snip>
DemoUtils.setSnippet("JXTable display properties", oscarTable);
DemoUtils.setSnippet("JXTable column properties", oscarTable.getTableHeader());
DemoUtils.setSnippet("Filter control", filterField, winnersCheckbox, tableStatus, tableRows);
DemoUtils.setSnippet("Use SwingWorker to asynchronously load the data", statusBarLeft,
(JComponent) statusBarLeft.getParent());
}
/**
* Binds components to data and user interaction.
*/
protected void bind() {
//<snip> JXTable data properties
oscarModel = new OscarTableModel();
// set the table model after setting the factory
oscarTable.setModel(oscarModel);
// </snip>
// <snip> Filter control
// create the controller
filterController = new OscarFiltering(oscarTable);
// bind controller properties to input components
BindingGroup filterGroup = new BindingGroup();
filterGroup.addBinding(Bindings.createAutoBinding(READ,
winnersCheckbox, BeanProperty.create("selected"),
filterController, BeanProperty.create("showOnlyWinners")));
filterGroup.addBinding(Bindings.createAutoBinding(READ,
filterField, BeanProperty.create("text"),
filterController, BeanProperty.create("filterString")));
// PENDING JW: crude hack to update the statusbar - fake property
// how-to do cleanly?
filterGroup.addBinding(Bindings.createAutoBinding(READ,
filterController, BeanProperty.create("showOnlyWinners"),
this, BeanProperty.create("statusContent")));
filterGroup.addBinding(Bindings.createAutoBinding(READ,
filterController, BeanProperty.create("filterString"),
this, BeanProperty.create("statusContent")));
filterGroup.bind();
// </snip>
oscarModel.addTableModelListener(new TableModelListener() {
public void tableChanged(TableModelEvent e) {
updateStatusBar();
}
});
//<snip> JXTable column properties
// some display properties can be configured only after the model has been set, here:
// configure the view sequence of columns to be different from the model
oscarTable.setColumnSequence(new Object[] {"yearColumn", "categoryColumn", "movieTitleColumn", "nomineesColumn"});
// </snip>
}
/**
* Binding artefact method: crude hack to update the status bar on state changes
* from the controller.
*/
public void setStatusContent(Object dummy) {
updateStatusBar();
}
/**
* Updates status labels. Called during loading and on
* changes to the filter controller state.
*/
protected void updateStatusBar() {
tableStatus.setName(filterController.isFilteringByString()
? "searchCountLabel" : "rowCountLabel");
DemoUtils.injectResources(this, tableStatus);
tableRows.setText("" + oscarTable.getRowCount());
}
/**
* Callback method for demo loader.
*/
public void start() {
if (oscarModel.getRowCount() != 0) return;
//<snip>Use SwingWorker to asynchronously load the data
// create SwingWorker which will load the data on a separate thread
SwingWorker<?, ?> loader = new OscarDataLoader(
XTableDemo.class.getResource("resources/oscars.xml"), oscarModel);
// display progress bar while data loads
progressBar = new JProgressBar();
statusBarLeft.add(progressBar);
// bind the worker's progress notification to the progressBar
// and the worker's state notification to this
BindingGroup group = new BindingGroup();
group.addBinding(Bindings.createAutoBinding(READ,
loader, BeanProperty.create("progress"),
progressBar, BeanProperty.create("value")));
group.addBinding(Bindings.createAutoBinding(READ,
loader, BeanProperty.create("state"),
this, BeanProperty.create("loadState")));
group.bind();
loader.execute();
// </snip>
}
/**
* Callback for worker's state notification: cleanup if done.
* @param state
*/
public void setLoadState(StateValue state) {
//<snip>Use SwingWorker to asynchronously load the data
// remove progressbar if done loading
if (state != StateValue.DONE) return;
statusBarLeft.remove(progressBar);
statusBarLeft.remove(actionStatus);
revalidate();
repaint();
// </snip>
}
//<snip>Use SwingWorker to asynchronously load the data
// specialized on OscarCandidate
private class OscarDataLoader extends SwingWorker<List<OscarCandidate>, OscarCandidate> {
private final URL oscarData;
private final OscarTableModel oscarModel;
private final List<OscarCandidate> candidates = new ArrayList<OscarCandidate>();
// </snip>
private JLabel credits;
private OscarDataLoader(URL oscarURL, OscarTableModel oscarTableModel) {
this.oscarData = oscarURL;
this.oscarModel = oscarTableModel;
}
//<snip>Use SwingWorker to asynchronously load the data
// background task let a parser do its stuff and
// update a progress bar
@Override
public List<OscarCandidate> doInBackground() {
OscarDataParser parser = new OscarDataParser() {
@Override
protected void addCandidate(OscarCandidate candidate) {
candidates.add(candidate);
if (candidates.size() % 3 == 0) {
try { // slow it down so we can see progress :-)
Thread.sleep(1);
} catch (Exception ex) {
}
}
publish(candidate);
setProgress(100 * candidates.size() / 8545);
}
};
parser.parseDocument(oscarData);
return candidates;
}
// </snip>
@Override
protected void process(List<OscarCandidate> moreCandidates) {
if (credits == null) {
showCredits();
}
oscarModel.add(moreCandidates);
}
// For older Java 6 on OS X
@SuppressWarnings("unused")
protected void process(OscarCandidate... moreCandidates) {
for (OscarCandidate candidate : moreCandidates) {
oscarModel.add(candidate);
}
}
//<snip>Use SwingWorker to asynchronously load the data
// show a transparent overlay on start loading
private void showCredits() {
credits = new JLabel();
credits.setName("credits");
credits.setFont(UIManager.getFont("Table.font").deriveFont(24f));
credits.setHorizontalAlignment(JLabel.CENTER);
credits.setBorder(new CompoundBorder(new TitledBorder(""),
new EmptyBorder(20,20,20,20)));
dataPanel.showMessageLayer(credits, .75f);
DemoUtils.injectResources(XTableDemo.this, dataPanel);
}
// </snip>
@Override
//<snip>Use SwingWorker to asynchronously load the data
// hide transparend overlay on end loading
protected void done() {
setProgress(100);
dataPanel.hideMessageLayer();
}
// </snip>
}
//------------------ init ui
//<snip> JXTable display properties
// center column header text
private JXTable createXTable() {
JXTable table = new JXTable() {
@Override
protected JTableHeader createDefaultTableHeader() {
return new JXTableHeader(columnModel) {
@Override
public void updateUI() {
super.updateUI();
// need to do in updateUI to survive toggling of LAF
if (getDefaultRenderer() instanceof JLabel) {
((JLabel) getDefaultRenderer()).setHorizontalAlignment(JLabel.CENTER);
}
}
// </snip>
};
}
};
return table;
}
protected void initComponents() {
setLayout(new BorderLayout());
controlPanel = createControlPanel();
add(controlPanel, BorderLayout.NORTH);
oscarTable = createXTable();
oscarTable.setName("oscarTable");
JScrollPane scrollpane = new JScrollPane(oscarTable);
dataPanel = new Stacker(scrollpane);
add(dataPanel, BorderLayout.CENTER);
add(createStatusBar(), BorderLayout.SOUTH);
}
protected JPanel createControlPanel() {
JPanel controlPanel = new JPanel();
GridBagLayout gridbag = new GridBagLayout();
GridBagConstraints c = new GridBagConstraints();
controlPanel.setLayout(gridbag);
c.gridx = 0;
c.gridy = 1;
c.gridheight = 1;
c.insets = new Insets(20, 10, 0, 10);
c.anchor = GridBagConstraints.SOUTHWEST;
JLabel searchLabel = new JLabel();
searchLabel.setName("searchLabel");
controlPanel.add(searchLabel, c);
c.gridx = 0;
c.gridy = 2;
c.weightx = 1.0;
c.insets.top = 0;
c.insets.bottom = 12;
c.anchor = GridBagConstraints.SOUTHWEST;
//c.fill = GridBagConstraints.HORIZONTAL;
filterField = new JTextField(24);
controlPanel.add(filterField, c);
c.gridx = 1;
c.gridy = 2;
c.gridwidth = GridBagConstraints.REMAINDER;
//c.insets.right = 24;
//c.insets.left = 12;
c.weightx = 0.0;
c.anchor = GridBagConstraints.EAST;
c.fill = GridBagConstraints.NONE;
winnersCheckbox = new JCheckBox();
winnersCheckbox.setName("winnersLabel");
controlPanel.add(winnersCheckbox, c);
return controlPanel;
}
protected Container createStatusBar() {
JXStatusBar statusBar = new JXStatusBar();
statusBar.putClientProperty("auto-add-separator", Boolean.FALSE);
// Left status area
statusBar.add(Box.createRigidArea(new Dimension(10, 22)));
statusBarLeft = Box.createHorizontalBox();
statusBar.add(statusBarLeft, JXStatusBar.Constraint.ResizeBehavior.FILL);
actionStatus = new JLabel();
actionStatus.setName("loadingStatusLabel");
actionStatus.setHorizontalAlignment(JLabel.LEADING);
statusBarLeft.add(actionStatus);
// Middle (should stretch)
// statusBar.add(Box.createHorizontalGlue());
// statusBar.add(Box.createHorizontalGlue());
statusBar.add(Box.createVerticalGlue());
statusBar.add(Box.createRigidArea(new Dimension(50, 0)));
// Right status area
tableStatus = new JLabel();
tableStatus.setName("rowCountLabel");
JComponent bar = Box.createHorizontalBox();
bar.add(tableStatus);
tableRows = new JLabel("0");
bar.add(tableRows);
statusBar.add(bar);
statusBar.add(Box.createHorizontalStrut(12));
return statusBar;
}
public static void main(String args[]) {
javax.swing.SwingUtilities.invokeLater(new Runnable() {
public void run() {
JXFrame frame = new JXFrame("JXTable Demo", true);
XTableDemo demo = new XTableDemo();
frame.add(demo);
frame.setSize(700, 400);
frame.setVisible(true);
demo.start();
}
});
}
//-----do nothing methods (keep beansbinding happy)
public Object getStatusContent() {
return null;
}
public StateValue getLoadState() {
return null;
}
}