package org.wordcorr.gui;
import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.FlowLayout;
import java.awt.Font;
import java.awt.event.ActionEvent;
import java.util.*;
import javax.swing.*;
import javax.swing.event.*;
import javax.swing.table.AbstractTableModel;
import javax.swing.table.TableColumn;
import org.wordcorr.AppProperties;
import org.wordcorr.BeanCatalog;
import org.wordcorr.db.Alignment;
import org.wordcorr.db.DatabaseException;
import org.wordcorr.db.Entry;
import org.wordcorr.db.Group;
import org.wordcorr.db.Persistent;
import org.wordcorr.db.Variety;
import org.wordcorr.db.View;
import org.wordcorr.db.WordCollection;
import org.wordcorr.db.Setting;
import org.wordcorr.gui.action.WordCorrAction;
import org.wordcorr.gui.input.AlignmentVectorTextField;
import org.wordcorr.gui.input.InputRow;
import org.wordcorr.gui.input.IPAKeyListener;
/**
* Pane for entering and editing data.
* @author Keith Hamasaki, Jim Shiba
**/
class AnnotatePane extends JPanel implements Refreshable {
private static final String[] COL_KEYS =
{ "lblTag", "lblAbbreviation", "lblShortName", "lblAligned", "lblBlank" };
/**
* Constructor.
**/
AnnotatePane(WordCollection collection) {
super(new BorderLayout());
_collection = collection;
_mainPanel = new ViewEntryPanel();
add(_mainPanel, BorderLayout.CENTER);
}
/**
* Refresh this pane.
**/
public void refresh() throws DatabaseException {
// set active view
_mainPanel.refresh();
_mainPanel.setVisible(true);
_mainPanel.selectDefault();
}
/**
* AddEditDelete panel for view entries.
**/
private final class ViewEntryPanel extends AddEditDeletePanel {
ViewEntryPanel() {
super(null, false);
// change find label
setFindButtonLabel(
AppPrefs.getInstance().getMessages().getString("btnFindAnnotate"));
this.setBorder(BorderFactory.createEtchedBorder());
getList().setModel(new BasicListModel());
getList().setFont(FontCache.getFont(FontCache.PRIMARY_GLOSS));
this.setVisible(false);
getList()
.getSelectionModel()
.addListSelectionListener(new ListSelectionListener() {
public synchronized void valueChanged(ListSelectionEvent evt) {
if (evt.getValueIsAdjusting()) {
return;
}
EntryWrapper entry = (EntryWrapper) getList().getSelectedValue();
if (entry != null) {
try {
Setting setting = _collection.getDatabase().getCurrentSetting();
setting.setEntryID(entry.getID());
setting.save();
} catch (DatabaseException e) {
e.printStackTrace();
}
}
}
});
}
/**
* Select the default entry.
**/
void selectDefault() throws DatabaseException {
Setting setting = _collection.getDatabase().getCurrentSetting();
for (int i = 0; i < getList().getModel().getSize(); i++) {
EntryWrapper entry = (EntryWrapper) getList().getModel().getElementAt(i);
if (entry.getID() == setting.getEntryID()) {
getList().setSelectedValue(entry, true);
break;
}
}
}
/**
* Additional refresh behavior for this component.
**/
public void refreshExt() throws DatabaseException {
// populate list
Setting setting = _collection.getDatabase().getCurrentSetting();
View view = _collection.getViewByID(setting.getViewID());
if (view != null) {
view.revert();
}
List entries = _collection.getEntries();
List wrappers = new ArrayList(entries.size());
for (Iterator it = entries.iterator(); it.hasNext();) {
wrappers.add(new EntryWrapper((Entry) it.next(), view));
}
((BasicListModel) getList().getModel()).setData(wrappers);
// set selection
selectDefault();
}
protected FindDialog createFindDialog() {
return new FindDialog("GlossFindDialog");
}
}
/**
* Wrapper bean class for an entry.
**/
public static final class EntryWrapper implements Persistent {
EntryWrapper(Entry entry, View view) throws DatabaseException {
_entry = entry;
_view = view;
}
// Attributes
public long getID() {
return _entry.getID();
}
public Integer getEntry() {
return _entry.getEntryNum();
}
public Entry getEntryObject() {
return _entry;
}
public String getGloss() {
return _entry.getName();
}
public String getGloss2() {
return _entry.getGloss2();
}
public List getData() {
try {
if (_alignments == null && _view != null) {
_alignments = _view.getAlignments(_entry);
}
} catch (DatabaseException e) {
e.printStackTrace();
}
return _alignments;
}
public void setData(List data) {
_alignments = data;
setDirty();
}
// Persistent methods
public String checkValidation() throws DatabaseException {
return null;
}
public void save() throws DatabaseException {
clearDirty();
for (Iterator it = getData().iterator(); it.hasNext();) {
((Alignment) it.next()).save();
}
_view.deleteUnusedGroups(_entry);
}
public void delete() throws DatabaseException {
clearDirty();
}
public void revert() throws DatabaseException {
clearDirty();
if (_view != null) {
_alignments = _view.getAlignments(_entry);
}
}
public boolean isDirty() {
return _dirty;
}
public void setDirty() {
_dirty = true;
}
public void clearDirty() {
_dirty = false;
}
public boolean isNew() {
return false;
}
View getView() {
return _view;
}
public String toString() {
return (_dirty ? "+> " : "") + _entry.getName();
}
private boolean _dirty = false;
private final Entry _entry;
private final View _view;
private List _alignments;
}
/**
* Input Row for alignment data.
**/
public static final class AlignmentRow extends InputRow {
public AlignmentRow(
BeanCatalog.Property prop,
Object obj,
final Refreshable refresh) {
super(prop, obj);
_entry = (EntryWrapper) obj;
final AlignmentTableModel model = new AlignmentTableModel();
model.addTableModelListener(new TableModelListener() {
public void tableChanged(TableModelEvent evt) {
_entry.setData(model.getRows());
try {
refresh.refresh();
} catch (DatabaseException e) {
Dialogs.genericError(e);
}
}
});
// set varieties for sorting
model.setVarieties(_entry.getView().getMembers());
_table = new AlignedTable(model, 3);
_table.setRowHeight(
new Double(
Math.ceil(
new JTextField().getFont().getSize()
* Double.parseDouble(AppProperties.getProperty("RowHeightFactor"))
+ _table.getRowMargin() * 2))
.intValue());
// set initial column widths
_table.setAutoCreateColumnsFromModel(false);
int[] colWidth = { 10, 10, 100, 300, 10 };
for (int i = 0; i < 5; i++) {
TableColumn col = _table.getColumnModel().getColumn(i);
col.setPreferredWidth(colWidth[i]);
}
_table.addMouseListener(new java.awt.event.MouseAdapter() {
public void mouseClicked(java.awt.event.MouseEvent evt) {
if (evt.getClickCount() == 2
&& ((evt.getModifiers() & evt.BUTTON1_MASK) > 0)) {
doEdit();
}
}
});
_table.addKeyListener(new java.awt.event.KeyAdapter() {
public void keyPressed(java.awt.event.KeyEvent evt) {
if (evt.getKeyCode() == java.awt.event.KeyEvent.VK_ENTER) {
doEdit();
evt.consume();
}
}
});
// setup buttons
JPanel btnpanel = new JPanel(new WrapFlowLayout(FlowLayout.LEFT));
btnpanel.add(new WButton(new WordCorrAction("btnEditDatum", "accEditDatum") {
public void actionPerformed(ActionEvent evt) {
doEdit();
}
}), BorderLayout.NORTH);
btnpanel
.add(new WButton(new WordCorrAction(
"btnAnnotateCopyVector",
"accAnnotateCopyVector") {
public void actionPerformed(ActionEvent evt) {
doCopyVector();
}
}), BorderLayout.NORTH);
_replaceVectorButton =
new WButton(new WordCorrAction(
"btnAnnotateReplaceVector",
"accAnnotateReplaceVector") {
public void actionPerformed(ActionEvent evt) {
doReplaceVector();
}
});
_replaceVectorButton.setEnabled(false);
btnpanel.add(_replaceVectorButton, BorderLayout.NORTH);
btnpanel
.add(new WButton(new WordCorrAction(
"btnGraphemeClusterDefine",
"accGraphemeClusterDefine") {
public void actionPerformed(ActionEvent evt) {
_alignmentVectorTextField.defineGraphemeCluster();
}
}), BorderLayout.NORTH);
btnpanel
.add(new WButton(new WordCorrAction(
"btnGraphemeClusterUncluster",
"accGraphemeClusterUncluster") {
public void actionPerformed(ActionEvent evt) {
_alignmentVectorTextField.unclusterGraphemeCluster();
}
}), BorderLayout.NORTH);
// assemble panel
JPanel panel = new JPanel(new BorderLayout());
panel.add(btnpanel, BorderLayout.NORTH);
panel.add(new JScrollPane(_table), BorderLayout.CENTER);
setupVectorText(_table.getColumnModel().getColumn(3));
// monitor group tag change
JTextField textField = new JTextField();
textField.addKeyListener(new IPAKeyListener(textField));
textField.getDocument().addDocumentListener(new DocumentListener() {
public void insertUpdate(DocumentEvent evt) {
docUpdate(evt);
}
public void removeUpdate(DocumentEvent evt) {
// note: if all characters deleted, unwanted edit message appears.
}
public void changedUpdate(DocumentEvent evt) {
docUpdate(evt);
}
private void docUpdate(DocumentEvent evt) {
try {
javax.swing.text.Document doc = evt.getDocument();
_table.getModel().setValueAt(
doc.getText(0, doc.getLength()),
_table.getSelectedRow(),
0);
} catch (javax.swing.text.BadLocationException e) {
// this should not happen
e.printStackTrace();
}
}
});
_table.getColumnModel().getColumn(0).setCellEditor(
new DefaultCellEditor(textField));
init(panel, _table);
}
public void setupVectorText(TableColumn col) {
//Set up the editor for the vector cells.
DefaultCellEditor editor = (DefaultCellEditor) col.getCellEditor();
if (editor == null) {
_alignmentVectorTextField = new AlignmentVectorTextField();
_alignmentVectorTextField.setFont(FontCache.getIPA());
col.setCellEditor(new DefaultCellEditor(_alignmentVectorTextField) {
public Component getTableCellEditorComponent(
JTable table,
Object value,
boolean isSelected,
int row,
int column) {
Alignment alignment = (Alignment) value;
AlignmentVectorTextField comp = (AlignmentVectorTextField) getComponent();
comp.setAlignment(alignment);
comp.setValue(alignment.getVector());
// Note: Need to bypass AlignmentDocument.insertString because
// getTableCellEditorComponent makes call to setup AlignedDatum value
// which causes illegal character message to be invoked.
(
(AlignmentVectorTextField.AlignmentDocument) comp
.getDocument())
.setDisableInsertString(
true);
Component editor =
super.getTableCellEditorComponent(table, value, isSelected, row, column);
(
(AlignmentVectorTextField.AlignmentDocument) comp
.getDocument())
.setDisableInsertString(
false);
return editor;
}
});
} else {
_alignmentVectorTextField = (AlignmentVectorTextField) editor.getComponent();
}
}
public void setValue(Object value) {
AlignmentTableModel model = (AlignmentTableModel) _table.getModel();
model.setRows((List) value);
_table.setAlignedPositionWidths((List) value, new AlignedDataExtractor() {
public String getColumnData(Object obj) {
Alignment data = (Alignment) obj;
return data.getAlignedDatum();
}
});
}
public Object getValue() {
return null;
}
public double getRowWeight() {
return 20.0;
}
private void doEdit() {
Alignment alignment =
((AlignmentTableModel) _table.getModel()).getRow(_table.getSelectedRow());
if (alignment == null)
return;
// recognize and propose grapheme clusters
recognizeGraphemeCluster(alignment);
AddDialog dialog = new AddDialog("pgtEditAlignment", alignment, null, false);
dialog.setVisible(true);
if (!dialog.isCancelled()) {
((AlignmentTableModel) _table.getModel()).refresh();
Group group = alignment.getGroup();
if (group != null && group.isDirty()) {
try {
group.save();
} catch (DatabaseException ignored) {}
}
// save changes
try {
_entry.save();
} catch (DatabaseException e) {
e.printStackTrace();
}
} else {
try {
// revert if windows closed without cancelling
alignment.revert();
} catch (DatabaseException e) {
e.printStackTrace();
}
}
}
/**
* Recognize and propose Grapheme Cluster Definition in Alignment from View list.
**/
private final void recognizeGraphemeCluster(Alignment alignment) {
Messages messages = AppPrefs.getInstance().getMessages();
try {
List clusters = alignment.getView().getGraphemeClusters();
String vector = alignment.getVector();
String alignedDatum = alignment.getAlignedDatum();
String datum = alignment.getDatum().getName();
int datumpos = 0;
boolean skip = false;
for (int i = 0; i < alignedDatum.length(); i++) {
switch (alignedDatum.charAt(i)) {
case Alignment.GRAPHEME_CLUSTER_START :
skip = true;
break;
case Alignment.GRAPHEME_CLUSTER_END :
skip = false;
break;
case Alignment.INDEL_SYMBOL :
case Alignment.EXCLUDE_SYMBOL :
break;
default :
if (!skip) {
// check in grapheme cluster list
boolean done = false;
for (Iterator it = clusters.iterator(); it.hasNext() && !done;) {
String graphemeCluster = (String) it.next();
if (alignedDatum.substring(i).startsWith(graphemeCluster)) {
// propose match
String proposal =
datum.substring(0, datumpos)
+ " {"
+ graphemeCluster
+ "} "
+ datum.substring(datumpos + graphemeCluster.length());
if (Dialogs
.confirm(
messages.getCompoundMessage(
"msgRecognizeGraphemeCluster",
new Object[] { graphemeCluster, proposal }))) {
// define grapheme cluster
String newvector = vector.substring(0, i) + Alignment.GRAPHEME_CLUSTER_START;
if (i + graphemeCluster.length() < alignedDatum.length()) {
newvector += vector.substring(i, i + graphemeCluster.length())
+ Alignment.GRAPHEME_CLUSTER_END
+ vector.substring(i + graphemeCluster.length());
} else {
// last cluster
newvector += vector.substring(i) + Alignment.GRAPHEME_CLUSTER_END;
}
alignment.setVector(newvector);
alignment.save();
vector = newvector;
alignedDatum = alignment.getAlignedDatum();
i += graphemeCluster.length() + 1;
datumpos += graphemeCluster.length() - 1;
done = true;
}
}
}
}
++datumpos;
break;
}
}
} catch (DatabaseException e) {
e.printStackTrace();
}
}
private void doCopyVector() {
Alignment alignment =
((AlignmentTableModel) _table.getModel()).getRow(_table.getSelectedRow());
if (alignment == null)
return;
// copy vector
Messages messages = AppPrefs.getInstance().getMessages();
if (Dialogs
.confirm(
messages.getCompoundMessage(
"msgAnnotateConfirmCopyVector",
alignment.getVector()))) {
_vector = alignment.getVector();
_replaceVectorButton.setEnabled(true);
}
}
private void doReplaceVector() {
int[] selectedRows = _table.getSelectedRows();
if (selectedRows.length == 0)
return;
Messages messages = AppPrefs.getInstance().getMessages();
if (Dialogs
.confirm(
messages.getCompoundMessage("msgAnnotateConfirmReplaceVector", _vector))) {
// get hold count in vector
int vectorHoldCount = 0;
for (int i = 0; i < _vector.length(); i++) {
char ch = _vector.charAt(i);
if (ch == Alignment.HOLD_SYMBOL) {
++vectorHoldCount;
}
}
// process each alignment
ArrayList alignmentExceptions = new ArrayList();
for (int i = 0; i < selectedRows.length; i++) {
Alignment alignment =
((AlignmentTableModel) _table.getModel()).getRow(selectedRows[i]);
if (alignment == null)
continue;
// check vector hold count
if (alignment.getDatum().getName().length() == vectorHoldCount) {
// replace vector
alignment.setVector(_vector);
} else {
alignmentExceptions.add(alignment);
}
}
// display exception list
if (!alignmentExceptions.isEmpty()) {
StringBuffer list = new StringBuffer();
for (Iterator it = alignmentExceptions.iterator(); it.hasNext();) {
Alignment exception = (Alignment) it.next();
list.append(
(list.length() == 0)
? exception.getDatum().getName()
: ", " + exception.getDatum().getName());
}
Dialogs.msgbox(
messages.getCompoundMessage(
"msgAnnotateReplaceVectorException",
list.toString()));
}
((AlignmentTableModel) _table.getModel()).refresh();
// save changes
try {
_entry.save();
} catch (DatabaseException e) {
e.printStackTrace();
}
}
}
private final EntryWrapper _entry;
private final AlignedTable _table;
private WButton _replaceVectorButton;
private String _vector = "";
}
/**
* Table model class for data elements.
**/
private static final class AlignmentTableModel
extends AbstractTableModel
implements Refreshable {
public Object getValueAt(int row, int col) {
Alignment alignment = (Alignment) _rows.get(row);
Variety variety = alignment.getDatum().getVariety();
Group group = alignment.getGroup();
switch (col) {
case 0 :
return group == null ? null : group.getName();
case 1 :
return variety.getAbbreviation();
case 2 :
return variety.getShortName();
case 3 :
// alignment for alignment vector
return alignment;
case 4 :
// determine Metathesis and Remarks flags
String flags = "";
if (!(alignment.getMetathesis().equals("0, 0, 0, 0")
|| alignment.getMetathesis().equals("")))
flags += "M";
if (!alignment.getObservations().equals(""))
flags += "R";
return flags;
default :
return "";
}
}
/*
* Set values for editable table cells.
*/
public void setValueAt(Object value, int row, int col) {
// group tag
if (col == 0) {
String tag = ((String) value).trim();
Alignment alignment = (Alignment) _rows.get(row);
// set default to ?
if (tag.equals("")) {
Dialogs.msgbox(
AppPrefs.getInstance().getMessages().getCompoundMessage(
"cmpRequiredCell",
"Tag"));
tag = "?";
}
// set Group Tag
try {
View view = alignment.getView();
Entry entry = alignment.getDatum().getEntry();
Group group = view.getGroup(tag, entry);
alignment.setGroup(group);
alignment.save();
view.deleteUnusedGroups(entry);
} catch (DatabaseException e) {
e.printStackTrace();
}
} else if (col == 3) {
Alignment alignment = (Alignment) _rows.get(row);
// set Vector
try {
// get vector from text field
alignment.setVector((String) _alignmentVectorTextField.getValue());
alignment.save();
} catch (DatabaseException e) {
e.printStackTrace();
}
}
// indicate change
fireTableCellUpdated(row, col);
}
/*
* Indicate editable table cells.
*/
public boolean isCellEditable(int row, int col) {
return col == 0 || col == 3;
}
public int getColumnCount() {
return 5;
}
public String getColumnName(int i) {
return AppPrefs.getInstance().getMessages().getString(COL_KEYS[i]);
}
public int getRowCount() {
return _rows.size();
}
Alignment getRow(int index) {
return (index < 0 || index >= _rows.size())
? null
: (Alignment) _rows.get(index);
}
List getRows() {
return new ArrayList(_rows);
}
void setRows(List rows) {
_rows.clear();
_rows.addAll(rows);
refresh();
}
void setVarieties(List varieties) {
_varieties.clear();
_varieties.addAll(varieties);
}
void sortRows() {
Collections.sort(_rows, new Comparator() {
public int compare(Object o1, Object o2) {
Alignment a1 = (Alignment) o1;
Alignment a2 = (Alignment) o2;
Group g1 = a1.getGroup();
Group g2 = a2.getGroup();
// sort by group first
if (g1 != null && g2 != null) {
String t1 = a1.getGroup().getName();
String t2 = a2.getGroup().getName();
if (t1 != null && t2 != null) {
int comp = t1.compareTo(t2);
if (comp != 0)
return comp;
}
}
// sort by variety
int i1 = _varieties.indexOf(a1.getDatum().getVariety());
int i2 = _varieties.indexOf(a2.getDatum().getVariety());
return i1 < i2 ? -1 : i1 > i2 ? 1 : 0;
}
});
}
public void refresh() {
// sort order of datums
sortRows();
fireTableStructureChanged();
}
private final List _rows = new ArrayList();
private final List _varieties = new ArrayList();
}
private final ViewEntryPanel _mainPanel;
private final WordCollection _collection;
private static AlignmentVectorTextField _alignmentVectorTextField;
}