java.fedora.client.objecteditor.DatastreamBindingPane Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of fcrepo-client Show documentation
Show all versions of fcrepo-client Show documentation
The Fedora Client is a Java Library that allows API access to a Fedora Repository. The client is typically one part of a full Fedora installation.
The newest version!
/*
* -----------------------------------------------------------------------------
*
* License and Copyright: The contents of this file are subject to the
* Apache License, Version 2.0 (the "License"); you may not use
* this file except in compliance with the License. You may obtain a copy of
* the License at
* http://www.fedora-commons.org/licenses.
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for
* the specific language governing rights and limitations under the License.
*
* The entire file consists of original code.
* Copyright © 2008 Fedora Commons, Inc.
*
Copyright © 2002-2007 The Rector and Visitors of the University of
* Virginia and Cornell University
* All rights reserved.
*
* -----------------------------------------------------------------------------
*/
package fedora.client.objecteditor;
import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Insets;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.Set;
import java.util.SortedMap;
import java.util.SortedSet;
import java.util.TreeMap;
import java.util.TreeSet;
import javax.swing.BorderFactory;
import javax.swing.Box;
import javax.swing.DefaultCellEditor;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JEditorPane;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTabbedPane;
import javax.swing.JTable;
import javax.swing.JTextField;
import javax.swing.ListSelectionModel;
import javax.swing.SwingUtilities;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import javax.swing.event.TableModelEvent;
import javax.swing.event.TableModelListener;
import javax.swing.table.AbstractTableModel;
import javax.swing.table.TableModel;
import fedora.client.Administrator;
import fedora.client.objecteditor.types.DatastreamBindingRule;
import fedora.client.objecteditor.types.DatastreamInputSpec;
import fedora.server.types.gen.DatastreamBinding;
/**
* Binding pane for datastreams.
*
*/
public class DatastreamBindingPane
extends JPanel
implements PotentiallyDirty {
private static final long serialVersionUID = 1L;
// private Datastream[] m_datastreams;
private ObjectEditorFrame m_gramps;
private DatastreamInputSpec m_spec;
private HashMap m_ruleForKey;
private HashMap m_perkyPanels;
private EditingPane m_owner;
private static DatastreamBindingComparator s_dsBindingComparator=
new DatastreamBindingComparator();
static ImageIcon notFulfilledIcon=new ImageIcon(Administrator.cl.getResource("images/fedora/exclaim16.gif"));
static ImageIcon fulfilledIcon=new ImageIcon(Administrator.cl.getResource("images/fedora/checkmark16.gif"));
private JTabbedPane m_bindingTabbedPane;
private ValidityListener m_validityListener;
public DatastreamBindingPane(ObjectEditorFrame gramps,
DatastreamBinding[] initialBindings,
String bMechPID,
DatastreamInputSpec spec,
ValidityListener validityListener, // ok if null
EditingPane owner) { // ok if null
m_validityListener=validityListener;
m_gramps = gramps;
m_owner=owner;
// m_datastreams=currentVersions;
m_spec=spec;
m_perkyPanels=new HashMap();
// put rules in a hash by key so they're easy to use later
m_ruleForKey=new HashMap();
for (int i=0; i0) {
m_table.addRowSelectionInterval(0, 0);
}
JPanel middlePane=new JPanel(new BorderLayout());
middlePane.add(new JScrollPane(m_table), BorderLayout.CENTER);
GridBagLayout gridbag=new GridBagLayout();
JPanel buttonPane=new JPanel(gridbag);
buttonPane.setBorder(BorderFactory.createEmptyBorder(0,4,0,0));
GridBagConstraints c=new GridBagConstraints();
c.gridx=0;
c.fill=GridBagConstraints.BOTH;
c.anchor=GridBagConstraints.NORTH;
c.weightx=1.0;
c.weighty=0.0;
c.insets=new Insets(0,0,3,0);
m_addButton=new JButton("Add...");
gridbag.setConstraints(m_addButton, c);
Administrator.constrainHeight(m_addButton);
buttonPane.add(m_addButton);
m_addButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
m_cellEditor.stopCellEditing();
// first bring up a dialog with candidates to
// choose from.
String selected=getCandidateSelection();
if (selected!=null) {
// if that succeeds, we need to tell the model
// to update itself and fire change events
String[] parts=selected.split(" - ");
m_tableModel.addRow(parts[0], "Binding to " + (parts.length > 2 ? parts[2] : ""));
}
}
});
if (rule.orderMatters()) {
m_insertButton=new JButton("Insert...");
gridbag.setConstraints(m_insertButton, c);
Administrator.constrainHeight(m_insertButton);
buttonPane.add(m_insertButton);
}
m_removeButton=new JButton("Remove");
gridbag.setConstraints(m_removeButton, c);
Administrator.constrainHeight(m_removeButton);
buttonPane.add(m_removeButton);
m_removeButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
m_cellEditor.stopCellEditing();
// tell the model to update itself and fire change events
m_tableModel.removeRow(m_table.getSelectedRow());
}
});
if (rule.orderMatters()) {
Component strut=Box.createVerticalStrut(6);
gridbag.setConstraints(strut, c);
buttonPane.add(strut);
m_upButton=new JButton("Up");
gridbag.setConstraints(m_upButton, c);
Administrator.constrainHeight(m_upButton);
buttonPane.add(m_upButton);
m_downButton=new JButton("Down");
gridbag.setConstraints(m_downButton, c);
Administrator.constrainHeight(m_downButton);
buttonPane.add(m_downButton);
}
c.weighty=1.0;
c.fill=GridBagConstraints.VERTICAL;
Component glue=Box.createVerticalGlue();
gridbag.setConstraints(glue, c);
buttonPane.add(glue);
JPanel bottomPane=new JPanel(new BorderLayout());
bottomPane.setBorder(BorderFactory.createEmptyBorder(4,0,0,0));
bottomPane.add(middlePane, BorderLayout.CENTER);
bottomPane.add(buttonPane, BorderLayout.EAST);
setLayout(new BorderLayout());
setBorder(BorderFactory.createEmptyBorder(4,4,4,4));
add(m_instructionPane, BorderLayout.NORTH);
add(bottomPane, BorderLayout.CENTER);
}
private String getCandidateSelection() {
String[] usedIDs=m_tableModel.getUsedDatastreamIDs();
String[] options=getCandidates(m_rule, usedIDs);
if (options.length==0) {
// bring up a dialog telling them there are no candidates
// to add, so they need to add one to the object
String more="";
if (usedIDs.length>0) more=" more";
JOptionPane.showInternalMessageDialog(Administrator.getDesktop(),
"There are no" + more + " datastreams of the required type\n"
+ "for this binding. Add one to the object first.",
"No candidates found",
JOptionPane.INFORMATION_MESSAGE);
return null;
} else {
StringBuffer instr=new StringBuffer();
instr.append("Choose a datastream:");
if (m_rule.getInputInstruction()!=null && m_rule.getInputInstruction().length()>0) {
instr.append("\n(");
instr.append(m_rule.getInputInstruction());
instr.append(')');
}
return (String) JOptionPane.showInputDialog(
Administrator.getDesktop(),
instr.toString(),
"New Binding",
JOptionPane.QUESTION_MESSAGE, null,
options, options[0]);
}
}
private void selectRowLater(final int rowNum) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
m_table.getSelectionModel().setSelectionInterval(rowNum, rowNum);
}
});
}
public void tableChanged(TableModelEvent e) {
DatastreamBindingTableModel model=(DatastreamBindingTableModel) e.getSource();
// update the shown selection of the table
if (e.getType()==TableModelEvent.INSERT) {
selectRowLater(e.getFirstRow());
} else if (e.getType()==TableModelEvent.DELETE) {
if (m_tableModel.getRowCount()>0) {
if (m_tableModel.getRowCount()==e.getFirstRow()) {
// if the row that was deleted was the last one,
// select the one just above it
selectRowLater(e.getFirstRow()-1);
} else {
// otherwise, select the one that's now in its place
selectRowLater(e.getFirstRow());
}
}
}
// update the dirty flag, then send the event up to datastreambindingpane
if (model.isDirty()) {
m_dirty=true;
} else {
m_dirty=false;
}
fireDataChanged(); // this will indirectly result in a call to
// doValidityCheck below
}
public boolean doValidityCheck() {
// check whether the model in its current state is valid
// if so or not, update the status text and button visibility
// appropriately, then return whether it's valid or not
boolean isValid=updateButtonsAndReturnValidity();
// do the appropriate completeness text updates
if (isValid && !m_wasValid) {
m_instructionPane.setText(m_instructionPane.getText().replaceAll("incomplete.", "complete."));
} else if (!isValid && m_wasValid) {
m_instructionPane.setText(m_instructionPane.getText().replaceAll("complete.", "incomplete."));
}
// remember this for next time so we don't have to do too much work
m_wasValid=isValid;
return isValid;
}
// check if it's valid, update buttons appropriately, and return
// if it's valid
private boolean updateButtonsAndReturnValidity() {
Set bindingSet=getBindings();
boolean couldUseMore=(m_rule.getMax()==-1) || (bindingSet.size()0);
}
// Remove : always exists; enabled if there's at least one binding
m_removeButton.setEnabled(bindingSet.size()>0);
// Up and Down : exist if orderMatters; enabled if > 1 binding
if (m_rule.orderMatters()) {
int row=m_table.getSelectedRow();
// Up : enabled if there's more than one binding and
// the current selection is below row 0
m_upButton.setEnabled(bindingSet.size()>1 && row>0);
// Down : enabled if there's more than one binding and
// the current selection is above the last row
m_downButton.setEnabled(bindingSet.size()>1 && row<(bindingSet.size()-1));
}
// now for returning the validity.
// we already know the types are valid and there aren't too many
// datastreams in the binding, because the widget doesn't allow
// that. the only thing that can possibly happen that's invalid
// is not enough datastreams.
return (bindingSet.size()>=m_rule.getMin());
}
// get a set of DatastreamBinding objects reflecting the current state
// of the model
public Set getBindings() {
return m_tableModel.getBindings();
}
public boolean isDirty() {
return m_dirty;
}
public void undoChanges() {
// undo the changes in the underlying table model by asking
// the current one for an unmodified copy
m_tableModel=m_tableModel.getOriginal();
m_table.setModel(m_tableModel);
m_tableModel.addTableModelListener(this);
m_table.getColumnModel().getColumn(0).setMinWidth(90);
m_table.getColumnModel().getColumn(0).setMaxWidth(90);
if (m_table.getRowCount()>0) {
m_table.addRowSelectionInterval(0, 0);
}
m_dirty=false;
// then make sure the view is updated
fireDataChanged();
}
private JEditorPane createInstructionPane(String bMechPID,
DatastreamBindingRule rule) {
StringBuffer buf=new StringBuffer();
// requires x to y datastreams...
buf.append("Binding of ");
if (rule.getInputLabel()!=null && rule.getInputLabel().length()>0) {
buf.append(rule.getInputLabel());
} else {
buf.append(rule.getKey());
}
buf.append(" is incomplete. Requires ");
if (rule.orderMatters() && (rule.getMax()==-1) || (rule.getMax()>1)) {
buf.append("an ordered list of ");
}
if (rule.getMin()==0) {
if (rule.getMax()==-1) {
buf.append("any number of datastreams");
} else {
buf.append("up to ");
if (rule.getMax()==1) {
buf.append("one");
} else {
buf.append(rule.getMax());
}
buf.append(" datastream");
if (rule.getMax()>1) {
buf.append('s');
}
}
} else {
buf.append("");
if (rule.getMin()==rule.getMax()) {
if (rule.getMin()==1) {
buf.append("one");
} else {
buf.append(rule.getMin());
}
buf.append(" datastream");
if (rule.getMax()>1) {
buf.append('s');
}
} else {
if (rule.getMin()==1) {
buf.append("one");
} else {
buf.append(rule.getMin());
}
if (rule.getMax()==-1) {
buf.append(" or more datastreams");
} else {
buf.append(" to ");
buf.append(rule.getMax());
buf.append(" datastreams");
}
}
}
// of type...
String[] types=rule.getTypes();
buf.append(" of ");
if (rule.accepts("*/*")) {
buf.append("any type.");
} else {
buf.append("type ");
buf.append("");
buf.append(types[0]);
buf.append("");
if (types.length==2) {
buf.append(" or ");
buf.append("");
buf.append(types[1]);
buf.append("");
} else if (types.length>2) {
for (int i=1; i");
buf.append(types[i]);
buf.append("");
}
}
}
// add inputLabel if available
if (rule.getInputInstruction()!=null
&& rule.getInputInstruction().length()>0) {
buf.append(" (");
buf.append(rule.getInputInstruction());
buf.append(")");
} else {
buf.append(".");
}
// finally, set up and return the Pane
JEditorPane result=new JEditorPane("text/html", buf.toString());
result.setEditable(false);
return result;
}
}
/**
* A TableCellEditor that bypasses swing's awkward "undo" behavior when
* the table loses focus during editing, and sets the table model
* appropriately on each editing event inside the textField,
* instead of waiting for the textField to lose focus.
*/
class NonCancelingCellEditor
extends DefaultCellEditor {
private static final long serialVersionUID = 1L;
private int m_row;
private int m_column;
private TableModel m_model;
private JTextField m_textField;
NonCancelingCellEditor(JTextField f, TableModel model) {
super(f);
m_textField=f;
m_model=model;
f.getDocument().addDocumentListener(new DocumentListener() {
public void changedUpdate(DocumentEvent e) {
dataChanged();
}
public void insertUpdate(DocumentEvent e) {
dataChanged();
}
public void removeUpdate(DocumentEvent e) {
dataChanged();
}
public void dataChanged() {
// update the model for each change
m_model.setValueAt(m_textField.getText(), m_row, m_column);
}
});
}
public Component getTableCellEditorComponent(JTable table,
Object value,
boolean isSelected,
int row,
int column) {
m_row=row;
m_column=column;
m_textField.setText((String) value);
return m_textField;
}
// Overridden to ignore cancels due to focus loss
public void cancelCellEditing() {
stopCellEditing();
}
}
class DatastreamBindingTableModel
extends AbstractTableModel
implements PotentiallyDirty {
private static final long serialVersionUID = 1L;
public DatastreamBinding[] m_bindings;
public DatastreamBinding[] m_originalBindings;
public String m_bindingKey;
public DatastreamBindingRule m_rule;
public DatastreamBindingTableModel(Set values, String bindingKey, DatastreamBindingRule rule) {
m_bindingKey=bindingKey;
m_rule=rule;
m_bindings=new DatastreamBinding[values.size()];
m_originalBindings=new DatastreamBinding[values.size()];
Iterator iter=values.iterator();
int i=0;
while (iter.hasNext()) {
DatastreamBinding n=(DatastreamBinding) iter.next();
m_bindings[i]=n;
m_originalBindings[i]=new DatastreamBinding();
m_originalBindings[i].setBindKeyName(new String(n.getBindKeyName()));
m_originalBindings[i].setBindLabel(new String(n.getBindLabel()));
m_originalBindings[i].setDatastreamID(new String(n.getDatastreamID()));
m_originalBindings[i].setSeqNo(new String(n.getSeqNo()));
i++;
}
}
public String getBindingKey() {
return m_bindingKey;
}
public Set getBindings() {
HashSet set=new HashSet();
for (int i=0; i
© 2015 - 2025 Weber Informatics LLC | Privacy Policy