/*
* Licensed Materials - Property of Rogue Wave Software, Inc.
* © Copyright Rogue Wave Software, Inc. 2014, 2017
* © Copyright IBM Corp. 2009, 2014
* © Copyright ILOG 1996, 2009
* All Rights Reserved.
*
* Note to U.S. Government Users Restricted Rights:
* The Software and Documentation were developed at private expense and
* are "Commercial Items" as that term is defined at 48 CFR 2.101,
* consisting of "Commercial Computer Software" and
* "Commercial Computer Software Documentation", as such terms are
* used in 48 CFR 12.212 or 48 CFR 227.7202-1 through 227.7202-4,
* as applicable.
*/
import ilog.views.gantt.IlvDuration;
import ilog.views.gantt.IlvGanttChart;
import ilog.views.gantt.IlvTimeUtil;
import ilog.views.gantt.action.IlvZoomToFitAction;
import ilog.views.gantt.property.IlvActivityUserDefinedProperty;
import ilog.views.gantt.swing.IlvConfigurableTableColumn;
import ilog.views.gantt.swing.IlvJTable;
import ilog.views.gantt.text.IlvDurationFormat;
import ilog.views.util.IlvProductUtil;
import ilog.views.util.IlvResourceUtil;
import ilog.views.util.event.IlvEventListenerCollection;
import ilog.views.util.event.IlvEventListenerList;
import ilog.views.util.styling.IlvStylingException;
import ilog.views.util.text.IlvDateFormatFactory;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseEvent;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.text.DateFormat;
import java.util.*;
import javax.swing.*;
import javax.swing.event.CellEditorListener;
import javax.swing.event.ChangeEvent;
import javax.swing.table.DefaultTableCellRenderer;
import javax.swing.table.TableCellEditor;
/**
* Dynamic Column sample.
* This sample code shows how to create table columns that display a
* particular property of an <code>IlvGeneralActivity</code> without
* requiring to code a custom subclass of <code>IlvStringColumn</code>.
*/
public class DynamicColumnSample extends JPanel {
public IlvGanttChart chart;
// The project configuration file that is used to load the general data model
static final String projectURL = "data/data.igpr";
// The zoom-to-fit action.
private IlvZoomToFitAction zoomToFitAction;
// The resource bundle
private ResourceBundle bundle;
public DynamicColumnSample(JFrame frame) {
// Load the resource bundles containing the locale dependent table
// header labels for the gantt chart.
bundle = IlvResourceUtil.getBundle("dynamicColumn.gantt",
getLocale(),
getClass().getClassLoader());
// Prepare the JPanel's appearance
setLayout(new BorderLayout());
JPanel panel = new JPanel();
chart = new IlvGanttChart();
try {
// Load the sample project
chart.setProject(getResourceURL(projectURL));
} catch (IOException e1) {
JOptionPane.showMessageDialog(frame,
e1,
"Error",
JOptionPane.ERROR_MESSAGE);
} catch (IlvStylingException e1) {
JOptionPane.showMessageDialog(frame,
e1,
"Error",
JOptionPane.ERROR_MESSAGE);
}
// A button to load the user-defined property columns.
Button buttonGantt =
new Button(bundle.getString("DynamicColumn.AddPropertyButton.Label"));
buttonGantt.addActionListener(new ActionListener()
{
Override
public void actionPerformed(ActionEvent e) {
// Let's define the user-defined properties, and their types.
// Note that this could be done by analysing the model.
// The array of property names from the model that can be added.
String[] propertyNames = {
"department",
"critical",
"latestStart",
"completion",
"priority",
"totalSlack"
};
//The array of property classes.
Class<?>[] propertyClasses = {
String.class,
Boolean.class,
Date.class,
Float.class,
Integer.class,
IlvDuration.class
};
// Create and add a column to the table for each property.
for (int i = 0; i < propertyNames.length; i++) {
String property = propertyNames[i];
Class<?> propertyClass = propertyClasses[i];
IlvJTable table = chart.getTable();
// Add the column to the chart, if not already there.
try {
if (table.getColumn(property) != null) {
// The column exists
return;
}
} catch (IllegalArgumentException ex) {
// the column does not exist.
}
// Create and add customized column, by doing the following actions:
// 1. Create the user-defined property adapter that can be accessed
// by the generic IlvStringProperty interface.
// 2. Customize formatting for this property adapter based on the
// property class.
// 3. Create the configurable table column for this property adapter.
// The table column can be customized with a table cell renderer,
// a cell editor, and a width size.
// 4. Add the table column to the table.
// Get the string for the header value
String headerValue = bundle.getString("DynamicColumn.HeaderColumn."
+ property);
// Create the user-defined property adapter that can be accessed via
// the generic IlvStringProperty interface.
IlvActivityUserDefinedProperty userDefinedProperty =
new IlvActivityUserDefinedProperty(property, propertyClass);
// Customize formatting based on the property class
if (propertyClass.isAssignableFrom(Date.class)) {
DateFormat dateFormat =
IlvDateFormatFactory.getDateInstance(DateFormat.DEFAULT,
chart.getULocale());
userDefinedProperty.setFormat(dateFormat);
} else if (propertyClass.isAssignableFrom(IlvDuration.class)) {
IlvDurationFormat durationFormat =
new IlvDurationFormat(IlvDurationFormat.TIME_UNIT_MEDIUM);
durationFormat.setLenientParseMode(true);
userDefinedProperty.setFormat(durationFormat);
}
// Create the configurable table column.
IlvConfigurableTableColumn propertyColumn;
// For the "priority" property use a slider as cell editor, and
// render the value with different colors.
if ("priority".equals(property)) {
propertyColumn =
new IlvConfigurableTableColumn(headerValue,
userDefinedProperty,
property,
new SliderEditor(0, 10, 0),
new PriorityRenderer());
} else if ("totalSlack".equals(property)) {
// For the "totalSlack" property, use a larger column.
propertyColumn =
new IlvConfigurableTableColumn(headerValue,
userDefinedProperty,
150,
property);
} else {
propertyColumn =
new IlvConfigurableTableColumn(headerValue,
userDefinedProperty,
property);
}
// Add the column to the table.
table.addColumn(propertyColumn);
}
}
});
panel.add(buttonGantt);
add(BorderLayout.CENTER, chart);
chart.revalidate();
zoomToFitAction = new IlvZoomToFitAction(chart, "", null, null, "", "");
zoomToFit();
// Make sure the full chart is visible.
chart.expandAllRows();
// Set the initially displayed time span to start 1 day before the beginning
// of the chart and extend for 3 weeks.
Date visibleStartTime =
IlvTimeUtil.subtract(chart.getGanttModel().getRootActivity().getStartTime(),
IlvDuration.ONE_DAY);
chart.setVisibleTime(visibleStartTime);
chart.setVisibleDuration(IlvDuration.ONE_WEEK.multiply(3));
add(BorderLayout.SOUTH, panel);
}
public static void main(String[] args) {
// This sample uses JViews Gantt features. When deploying an
// application that includes this code, you need to be in possession
// of a Rogue Wave JViews Gantt Deployment license.
IlvProductUtil.DeploymentLicenseRequired(
IlvProductUtil.JViews_Gantt_Deployment);
SwingUtilities.invokeLater(new Runnable() {
Override
public void run() {
JFrame frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setBounds(10, 400, 700, 400);
DynamicColumnSample example = new DynamicColumnSample(frame);
frame.getContentPane().add(example);
frame.setVisible(true);
}
});
}
// =========================================
// Accessing Resources
// =========================================
/**
* Returns the fully qualified URL of a resource file that is specified
* relative to the working directory of the example. Note that this is
* different than calling <code>Class.getResource()</code>, which performs
* resource lookup relative to the example's classpath.
*
* @param relativePath The path of the resource file, relative to the working
* directory of the example.
* @return The resource URL.
*
* @throws IOException if the path cannot be resolved to a valid and existing
* URL.
*/
public URL getResourceURL(String relativePath)
throws IOException {
relativePath = relativePath.replace('\\', '/');
URL url;
// In an application context, we try to find an external file relative to
// the current working directory. If an external file does not exist, then
// the file may be bundled into the jar with the classes. In this case, we
// prepend a '/' to relativePath so that we search relative to the
// classloader's root and not relative to the packaging of the current
// class.
File file = new File(relativePath);
// If the file exists in the external file system, we return its
// corresponding URL.
if (file.exists()) {
url = file.toURI().toURL();
}
// Otherwise, we search for the file relative to the classloader's root.
// This will find the file if it is packaged into a jar with the classes.
else {
// Prepend a '/' so that we search relative to the classloader's root and
// not relative to the packaging of the current class.
if (relativePath.charAt(0) != '/') {
relativePath = '/' + relativePath;
}
url = getClass().getResource(relativePath);
}
// Verify that we have a valid URL by trying to open its associated stream.
if (url == null) {
throw new FileNotFoundException(relativePath);
}
InputStream stream = url.openStream();
stream.close();
return url;
}
/**
* Zooms the chart to fit the data model's time interval.
*/
protected void zoomToFit() {
zoomToFitAction.perform();
}
// =========================================
// PriorityColumn.SliderEditor
// =========================================
/**
* <code>SliderEditor</code> is a JSlider table cell editor that can be used
* to edit an Integer of an <code>IlvGeneralActivity</code> within a table.
*/
static class SliderEditor extends JSlider implements TableCellEditor {
/**
* There is currently a bug in JSlider that causes refresh problems if we
* add the slider directly to the table. Instead, the workaround is to put
* the slider inside of a container and add the container to the table.
*/
private JPanel sliderContainer;
/**
* The list of cell editor listeners.
*/
private IlvEventListenerCollection<CellEditorListener> listeners
= new IlvEventListenerList<CellEditorListener>();
/**
* Creates a new horizontal <code>SliderEditor</code> .
*/
SliderEditor(int min, int max, int ini) {
super(HORIZONTAL, min, max, ini);
setPaintTicks(true);
setMajorTickSpacing(1);
setPaintLabels(true);
setSnapToTicks(true);
// Create custom tick labels for even values.
Font labelFont = new Font("Dialog", Font.PLAIN, 10);
Hashtable<Integer, JLabel> labels = new Hashtable<Integer, JLabel>();
for (int i = 0; i <= max; i += 2) {
JLabel label = new JLabel("" + i, JLabel.CENTER);
label.setFont(labelFont);
labels.put(i, label);
}
setLabelTable(labels);
// Put the slider inside a simple panel container. The container will
// be added to the table when editing starts, instead of the slider being
// added directly. This works around a current Swing bug in JSlider.
sliderContainer = new JPanel(new BorderLayout());
sliderContainer.add(this, BorderLayout.CENTER);
}
/**
* Returns the component that should be added to the table's component
* hierarchy to perform editing. Once installed in the table hierarchy this
* component will be able to draw and receive user input.
*
* <p>Instead of returning the slider, we return the lightweight container that
* the slider is in. This is a workaround for a current bug in JSlider.</p>
*
* @param table The JTable that is asking the editor to edit. This
* parameter can be <code>null</code>.
* @param value The value of the cell to be edited. This should either
* be a String for a valid priority value or
* <code>null</code>.
* @param isSelected The cell is to be rendered with selection
* highlighting.
* @param row The row of the cell being edited.
* @param column The column of the cell being edited.
* @return The component to edit.
*/
Override
public Component getTableCellEditorComponent(JTable table,
Object value,
boolean isSelected,
int row, int column) {
setValue(Integer.parseInt((String) value));
// Remove the comment from the following line for the slider to have the
// same color as the table.
// this.setBackground(table.getBackground());
//return sliderContainer;
return this;
}
/**
* Returns the value contained in the editor. This will be the slider's
* value as a String.
*/
Override
public Object getCellEditorValue() {
return String.valueOf(getValue());
}
/**
* Asks the editor whether it can start editing using
* <code>anEvent</code>.
* <code>anEvent</code> is in the invoking component coordinate system.
* The editor cannot assume the component returned by
* <code>getCellEditorComponent()</code> is installed. This method is
* intended for the use of the client to avoid the cost of setting up
* and installing the editor component if editing is not possible.
* If editing can be started this method returns <code>true</code>.
*
* @param anEvent The event the editor should use to consider whether to
* begin editing.
* @return <code>true</code> if editing can be started.
*
* @see #shouldSelectCell
*/
Override
public boolean isCellEditable(EventObject anEvent) {
if (anEvent instanceof MouseEvent) {
if (((MouseEvent) anEvent).getClickCount() >= 2) {
return true;
}
}
return false;
}
/**
* Specifies to the editor to start editing using <code>anEvent</code>.
* The editor itself then decides whether it wants to start editing in
* different states depending on the exact type of <code>anEvent</code>.
* For example, with a text field editor, if the event is a mouse event
* the editor might start editing with the cursor at the clicked point. If
* the event is a keyboard event, it might want to replace the value
* of the text field with that first key, and so on. <code>anEvent</code>
* is in the invoking component's coordinate system. A <code>null</code>
* value is a valid parameter for <code>anEvent</code>, and it is up to the
* editor to determine what is the default starting state. For example,
* a text field editor might want to select all the text and start
* editing if <code>anEvent</code> is <code>null</code>. The editor can
* assume the component returned by <code>getCellEditorComponent()</code>
* is properly installed in the client component hierarchy before this
* method is called.
* <p>
* The return value of <code>shouldSelectCell()</code> is a boolean
* indicating whether the editing cell should be selected. Typically,
* the return value is <code>true</code>, because in most cases the editing
* cell should be selected. However, it is useful to return
* <code>false</code> to keep the selection from changing for some types
* of edits. For example, in a table that
* contains a column of check boxes, the user might want to change
* these check boxes without altering the selection. (See Netscape
* Communicator for such an example.) Of course, it is up to
* the client of the editor to use the return value, but it does not
* need to if it does not want to.
*
* @param anEvent The event the editor should use to start editing.
* @return <code>true</code> if the editor would like the editing cell to
* be selected.
* @see #isCellEditable
*/
Override
public boolean shouldSelectCell(EventObject anEvent) {
return true;
}
/**
* Specifies to the editor to stop editing and accept any partially edited
* value as the value of the editor. The editor returns <code>false</code>
* if editing was not stopped. This is useful for editors that validate and
* cannot accept invalid entries.
*
* @return <code>true</code> if editing was stopped.
*/
Override
public boolean stopCellEditing() {
fireEditingStopped();
return true;
}
/**
* Specifies to the editor to cancel editing and not accept any partially
* edited value.
*/
Override
public void cancelCellEditing() {
fireEditingCanceled();
}
/**
* Adds a listener to the list that is notified when the editor starts,
* stops, or cancels editing.
*
* @param l The <code>CellEditorListener</code>.
*/
Override
public void addCellEditorListener(CellEditorListener l) {
listeners.addListener(l);
}
/**
* Removes a listener from the list that is notified.
*
* @param l The <code>CellEditorListener</code>.
*/
Override
public void removeCellEditorListener(CellEditorListener l) {
listeners.removeListener(l);
}
/**
* Notifies all registered listeners that editing has stopped.
*/
protected void fireEditingStopped() {
Iterator<CellEditorListener> i = listeners.getListeners();
if (i.hasNext()) {
ChangeEvent e = new ChangeEvent(this);
while (i.hasNext()) {
i.next().editingStopped(e);
}
}
}
/**
* Notifies all registered listeners that editing has been cancelled.
*/
protected void fireEditingCanceled() {
Iterator<CellEditorListener> i = listeners.getListeners();
if (i.hasNext()) {
ChangeEvent e = new ChangeEvent(this);
while (i.hasNext()) {
i.next().editingCanceled(e);
}
}
}
}
// =========================================
// Priority Column TableCellRenderer
// =========================================
/**
* Creates a renderer that will be used to render the cells in this column. For each
* cell, the renderer will be passed the <code>String</code> <em>value</em> returned by
* the column's {@link IlvConfigurableTableColumn#getValue} method. We start with a
* basic Swing <code>DefaultTableCellRenderer</code> and override the
* <code>getTableCellRendererComponent</code> method to set the text color based on the
* priority level.
*/
static class PriorityRenderer extends DefaultTableCellRenderer {
private Color darkGreen = Color.green.darker();
PriorityRenderer() {
setHorizontalAlignment(JLabel.CENTER);
}
Override
public Component getTableCellRendererComponent(JTable table, Object value,
boolean isSelected,
boolean hasFocus,
int row, int column) {
Component comp =
super.getTableCellRendererComponent(table, value, isSelected,
hasFocus, row, column);
if (value instanceof String
&& ((String) (value)).length() != 0) {
int intVal = Integer.parseInt((String) value);
Color color;
if (intVal <= 2) {
color = Color.red;
} else if (intVal <= 4) {
color = Color.orange;
} else if (intVal <= 7) {
color = darkGreen;
} else {
color = Color.blue;
}
comp.setForeground(color);
}
return comp;
}
}
}