import jalview.api.FeatureColourI;
import jalview.api.FeatureSettingsControllerI;
import jalview.bin.Cache;
-import jalview.datamodel.SequenceFeature;
+import jalview.datamodel.AlignmentI;
import jalview.datamodel.SequenceI;
import jalview.gui.Help.HelpId;
import jalview.io.JalviewFileChooser;
+import jalview.io.JalviewFileView;
import jalview.schemabinding.version2.JalviewUserColours;
import jalview.schemes.FeatureColour;
import jalview.util.Format;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
+import java.awt.Dimension;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.GridLayout;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.util.Arrays;
+import java.util.HashSet;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.List;
import javax.swing.JLabel;
import javax.swing.JLayeredPane;
import javax.swing.JMenuItem;
-import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JPopupMenu;
import javax.swing.JScrollPane;
import javax.swing.table.TableCellEditor;
import javax.swing.table.TableCellRenderer;
-public class FeatureSettings extends JPanel implements
- FeatureSettingsControllerI
+public class FeatureSettings extends JPanel
+ implements FeatureSettingsControllerI
{
DasSourceBrowser dassourceBrowser;
JPanel transPanel = new JPanel(new GridLayout(1, 2));
+ private static final int MIN_WIDTH = 400;
+
+ private static final int MIN_HEIGHT = 400;
+
+ /**
+ * when true, constructor is still executing - so ignore UI events
+ */
+ protected volatile boolean inConstruction = true;
+
+ /**
+ * Constructor
+ *
+ * @param af
+ */
public FeatureSettings(AlignFrame af)
{
this.af = af;
fr = af.getFeatureRenderer();
// allow transparency to be recovered
- transparency.setMaximum(100 - (int) ((originalTransparency = fr
- .getTransparency()) * 100));
+ transparency.setMaximum(100
+ - (int) ((originalTransparency = fr.getTransparency()) * 100));
try
{
MessageManager.getString("label.sequence_feature_settings"),
400, 450);
}
+ frame.setMinimumSize(new Dimension(MIN_WIDTH, MIN_HEIGHT));
- frame.addInternalFrameListener(new javax.swing.event.InternalFrameAdapter()
- {
- @Override
- public void internalFrameClosed(
- javax.swing.event.InternalFrameEvent evt)
- {
- fr.removePropertyChangeListener(change);
- dassourceBrowser.fs = null;
- };
- });
+ frame.addInternalFrameListener(
+ new javax.swing.event.InternalFrameAdapter()
+ {
+ @Override
+ public void internalFrameClosed(
+ javax.swing.event.InternalFrameEvent evt)
+ {
+ fr.removePropertyChangeListener(change);
+ dassourceBrowser.fs = null;
+ };
+ });
frame.setLayer(JLayeredPane.PALETTE_LAYER);
+ inConstruction = false;
}
protected void popupSort(final int selectedRow, final String type,
{
final FeatureColourI featureColour = (FeatureColourI) typeCol;
- JPopupMenu men = new JPopupMenu(MessageManager.formatMessage(
- "label.settings_for_param", new String[] { type }));
+ JPopupMenu men = new JPopupMenu(MessageManager
+ .formatMessage("label.settings_for_param", new String[]
+ { type }));
JMenuItem scr = new JMenuItem(
MessageManager.getString("label.sort_by_score"));
men.add(scr);
@Override
public void actionPerformed(ActionEvent e)
{
- me.af.avc.sortAlignmentByFeatureScore(Arrays
- .asList(new String[] { type }));
+ me.af.avc
+ .sortAlignmentByFeatureScore(Arrays.asList(new String[]
+ { type }));
}
});
@Override
public void actionPerformed(ActionEvent e)
{
- me.af.avc.sortAlignmentByFeatureDensity(Arrays
- .asList(new String[] { type }));
+ me.af.avc
+ .sortAlignmentByFeatureDensity(Arrays.asList(new String[]
+ { type }));
}
});
else
{
// probably the color chooser!
- table.setValueAt(
- new FeatureColour(colorChooser.getColor()),
+ table.setValueAt(new FeatureColour(colorChooser.getColor()),
selectedRow, 1);
table.validate();
me.updateFeatureRenderer(
false, type);
}
});
- JMenuItem clearCols = new JMenuItem(
- MessageManager.getString("label.select_columns_not_containing"));
+ JMenuItem clearCols = new JMenuItem(MessageManager
+ .getString("label.select_columns_not_containing"));
clearCols.addActionListener(new ActionListener()
{
@Override
private boolean handlingUpdate = false;
/**
- * contains a float[3] for each feature type string. created by setTableData
+ * holds {featureCount, totalExtent} for each feature type
*/
Map<String, float[]> typeWidth = null;
@Override
synchronized public void discoverAllFeatureData()
{
- Vector<String> allFeatures = new Vector<String>();
- Vector<String> allGroups = new Vector<String>();
- SequenceFeature[] tmpfeatures;
- String group;
- for (int i = 0; i < af.getViewport().getAlignment().getHeight(); i++)
- {
- tmpfeatures = af.getViewport().getAlignment().getSequenceAt(i)
- .getSequenceFeatures();
- if (tmpfeatures == null)
- {
- continue;
- }
+ Set<String> allGroups = new HashSet<>();
+ AlignmentI alignment = af.getViewport().getAlignment();
- int index = 0;
- while (index < tmpfeatures.length)
+ for (int i = 0; i < alignment.getHeight(); i++)
+ {
+ SequenceI seq = alignment.getSequenceAt(i);
+ for (String group : seq.getFeatures().getFeatureGroups(true))
{
- if (tmpfeatures[index].begin == 0 && tmpfeatures[index].end == 0)
- {
- index++;
- continue;
- }
-
- if (tmpfeatures[index].getFeatureGroup() != null)
- {
- group = tmpfeatures[index].featureGroup;
- if (!allGroups.contains(group))
- {
- allGroups.addElement(group);
- checkGroupState(group);
- }
- }
-
- if (!allFeatures.contains(tmpfeatures[index].getType()))
+ if (group != null && !allGroups.contains(group))
{
- allFeatures.addElement(tmpfeatures[index].getType());
+ allGroups.add(group);
+ checkGroupState(group);
}
- index++;
}
}
{
boolean visible = fr.checkGroupVisibility(group, true);
- if (groupPanel == null)
- {
- groupPanel = new JPanel();
- }
-
- boolean alreadyAdded = false;
for (int g = 0; g < groupPanel.getComponentCount(); g++)
{
if (((JCheckBox) groupPanel.getComponent(g)).getText().equals(group))
{
- alreadyAdded = true;
((JCheckBox) groupPanel.getComponent(g)).setSelected(visible);
- break;
+ return visible;
}
}
- if (alreadyAdded)
- {
-
- return visible;
- }
final String grp = group;
final JCheckBox check = new JCheckBox(group, visible);
check.setFont(new Font("Serif", Font.BOLD, 12));
+ check.setToolTipText(group);
check.addItemListener(new ItemListener()
{
@Override
synchronized void resetTable(String[] groupChanged)
{
- if (resettingTable == true)
+ if (resettingTable)
{
return;
}
resettingTable = true;
- typeWidth = new Hashtable<String, float[]>();
+ typeWidth = new Hashtable<>();
// TODO: change avWidth calculation to 'per-sequence' average and use long
// rather than float
- float[] avWidth = null;
- SequenceFeature[] tmpfeatures;
- String group = null, type;
- Vector<String> visibleChecks = new Vector<String>();
-
- // Find out which features should be visible depending on which groups
- // are selected / deselected
- // and recompute average width ordering
+
+ Set<String> displayableTypes = new HashSet<>();
+ Set<String> foundGroups = new HashSet<>();
+
+ /*
+ * determine which feature types may be visible depending on
+ * which groups are selected, and recompute average width data
+ */
for (int i = 0; i < af.getViewport().getAlignment().getHeight(); i++)
{
- tmpfeatures = af.getViewport().getAlignment().getSequenceAt(i)
- .getSequenceFeatures();
- if (tmpfeatures == null)
- {
- continue;
- }
+ SequenceI seq = af.getViewport().getAlignment().getSequenceAt(i);
- int index = 0;
- while (index < tmpfeatures.length)
+ /*
+ * get the sequence's groups for positional features
+ * and keep track of which groups are visible
+ */
+ Set<String> groups = seq.getFeatures().getFeatureGroups(true);
+ Set<String> visibleGroups = new HashSet<>();
+ for (String group : groups)
{
- group = tmpfeatures[index].featureGroup;
-
- if (tmpfeatures[index].begin == 0 && tmpfeatures[index].end == 0)
- {
- index++;
- continue;
- }
-
if (group == null || checkGroupState(group))
{
- type = tmpfeatures[index].getType();
- if (!visibleChecks.contains(type))
- {
- visibleChecks.addElement(type);
- }
- }
- if (!typeWidth.containsKey(tmpfeatures[index].getType()))
- {
- typeWidth.put(tmpfeatures[index].getType(),
- avWidth = new float[3]);
- }
- else
- {
- avWidth = typeWidth.get(tmpfeatures[index].getType());
- }
- avWidth[0]++;
- if (tmpfeatures[index].getBegin() > tmpfeatures[index].getEnd())
- {
- avWidth[1] += 1 + tmpfeatures[index].getBegin()
- - tmpfeatures[index].getEnd();
+ visibleGroups.add(group);
}
- else
+ }
+ foundGroups.addAll(groups);
+
+ /*
+ * get distinct feature types for visible groups
+ * record distinct visible types, and their count and total length
+ */
+ Set<String> types = seq.getFeatures().getFeatureTypesForGroups(true,
+ visibleGroups.toArray(new String[visibleGroups.size()]));
+ for (String type : types)
+ {
+ displayableTypes.add(type);
+ float[] avWidth = typeWidth.get(type);
+ if (avWidth == null)
{
- avWidth[1] += 1 + tmpfeatures[index].getEnd()
- - tmpfeatures[index].getBegin();
+ avWidth = new float[2];
+ typeWidth.put(type, avWidth);
}
- index++;
+ // todo this could include features with a non-visible group
+ // - do we greatly care?
+ // todo should we include non-displayable features here, and only
+ // update when features are added?
+ avWidth[0] += seq.getFeatures().getFeatureCount(true, type);
+ avWidth[1] += seq.getFeatures().getTotalFeatureLength(type);
}
}
- int fSize = visibleChecks.size();
- Object[][] data = new Object[fSize][3];
+ Object[][] data = new Object[displayableTypes.size()][3];
int dataIndex = 0;
if (fr.hasRenderOrder())
List<String> frl = fr.getRenderOrder();
for (int ro = frl.size() - 1; ro > -1; ro--)
{
- type = frl.get(ro);
+ String type = frl.get(ro);
- if (!visibleChecks.contains(type))
+ if (!displayableTypes.contains(type))
{
continue;
}
data[dataIndex][0] = type;
data[dataIndex][1] = fr.getFeatureStyle(type);
- data[dataIndex][2] = new Boolean(af.getViewport()
- .getFeaturesDisplayed().isVisible(type));
+ data[dataIndex][2] = new Boolean(
+ af.getViewport().getFeaturesDisplayed().isVisible(type));
dataIndex++;
- visibleChecks.removeElement(type);
+ displayableTypes.remove(type);
}
}
- fSize = visibleChecks.size();
- for (int i = 0; i < fSize; i++)
+ /*
+ * process any extra features belonging only to
+ * a group which was just selected
+ */
+ while (!displayableTypes.isEmpty())
{
- // These must be extra features belonging to the group
- // which was just selected
- type = visibleChecks.elementAt(i).toString();
+ String type = displayableTypes.iterator().next();
data[dataIndex][0] = type;
data[dataIndex][1] = fr.getFeatureStyle(type);
data[dataIndex][2] = new Boolean(true);
dataIndex++;
+ displayableTypes.remove(type);
}
if (originalData == null)
System.arraycopy(data[i], 0, originalData[i], 0, 3);
}
}
+ else
+ {
+ updateOriginalData(data);
+ }
table.setModel(new FeatureTableModel(data));
table.getColumnModel().getColumn(0).setPreferredWidth(200);
- if (groupPanel != null)
- {
- groupPanel.setLayout(new GridLayout(
- fr.getFeatureGroupsSize() / 4 + 1, 4));
-
- groupPanel.validate();
- bigPanel.add(groupPanel, BorderLayout.NORTH);
- }
+ groupPanel.setLayout(
+ new GridLayout(fr.getFeatureGroupsSize() / 4 + 1, 4));
+ pruneGroups(foundGroups);
+ groupPanel.validate();
updateFeatureRenderer(data, groupChanged != null);
resettingTable = false;
}
/**
+ * Updates 'originalData' (used for restore on Cancel) if we detect that
+ * changes have been made outwith this dialog
+ * <ul>
+ * <li>a new feature type added (and made visible)</li>
+ * <li>a feature colour changed (in the Amend Features dialog)</li>
+ * </ul>
+ *
+ * @param foundData
+ */
+ protected void updateOriginalData(Object[][] foundData)
+ {
+ // todo LinkedHashMap instead of Object[][] would be nice
+
+ Object[][] currentData = ((FeatureTableModel) table.getModel())
+ .getData();
+ for (Object[] row : foundData)
+ {
+ String type = (String) row[0];
+ boolean found = false;
+ for (Object[] current : currentData)
+ {
+ if (type.equals(current[0]))
+ {
+ found = true;
+ /*
+ * currently dependent on object equality here;
+ * really need an equals method on FeatureColour
+ */
+ if (!row[1].equals(current[1]))
+ {
+ /*
+ * feature colour has changed externally - update originalData
+ */
+ for (Object[] original : originalData)
+ {
+ if (type.equals(original[0]))
+ {
+ original[1] = row[1];
+ break;
+ }
+ }
+ }
+ break;
+ }
+ }
+ if (!found)
+ {
+ /*
+ * new feature detected - add to original data (on top)
+ */
+ Object[][] newData = new Object[originalData.length + 1][3];
+ for (int i = 0; i < originalData.length; i++)
+ {
+ System.arraycopy(originalData[i], 0, newData[i + 1], 0, 3);
+ }
+ newData[0] = row;
+ originalData = newData;
+ }
+ }
+ }
+
+ /**
+ * Remove from the groups panel any checkboxes for groups that are not in the
+ * foundGroups set. This enables removing a group from the display when the
+ * last feature in that group is deleted.
+ *
+ * @param foundGroups
+ */
+ protected void pruneGroups(Set<String> foundGroups)
+ {
+ for (int g = 0; g < groupPanel.getComponentCount(); g++)
+ {
+ JCheckBox checkbox = (JCheckBox) groupPanel.getComponent(g);
+ if (!foundGroups.contains(checkbox.getText()))
+ {
+ groupPanel.remove(checkbox);
+ }
+ }
+ }
+
+ /**
* reorder data based on the featureRenderers global priority list.
*
* @param data
void load()
{
- JalviewFileChooser chooser = new JalviewFileChooser(
- Cache.getProperty("LAST_DIRECTORY"), "fc",
- "Sequence Feature Colours", "Sequence Feature Colours");
- chooser.setFileView(new jalview.io.JalviewFileView());
- chooser.setDialogTitle(MessageManager
- .getString("label.load_feature_colours"));
+ JalviewFileChooser chooser = new JalviewFileChooser("fc",
+ "Sequence Feature Colours");
+ chooser.setFileView(new JalviewFileView());
+ chooser.setDialogTitle(
+ MessageManager.getString("label.load_feature_colours"));
chooser.setToolTipText(MessageManager.getString("action.load"));
int value = chooser.showOpenDialog(this);
try
{
- InputStreamReader in = new InputStreamReader(new FileInputStream(
- file), "UTF-8");
+ InputStreamReader in = new InputStreamReader(
+ new FileInputStream(file), "UTF-8");
JalviewUserColours jucs = JalviewUserColours.unmarshal(in);
void save()
{
- JalviewFileChooser chooser = new JalviewFileChooser(
- Cache.getProperty("LAST_DIRECTORY"), "fc",
- "Sequence Feature Colours", "Sequence Feature Colours");
- chooser.setFileView(new jalview.io.JalviewFileView());
- chooser.setDialogTitle(MessageManager
- .getString("label.save_feature_colours"));
+ JalviewFileChooser chooser = new JalviewFileChooser("fc",
+ "Sequence Feature Colours");
+ chooser.setFileView(new JalviewFileView());
+ chooser.setDialogTitle(
+ MessageManager.getString("label.save_feature_colours"));
chooser.setToolTipText(MessageManager.getString("action.save"));
int value = chooser.showSaveDialog(this);
col.setRGB(Format.getHexString(fcol.getMaxColour()));
col.setMin(fcol.getMin());
col.setMax(fcol.getMax());
- col.setMinRGB(jalview.util.Format.getHexString(fcol
- .getMinColour()));
+ col.setMinRGB(
+ jalview.util.Format.getHexString(fcol.getMinColour()));
col.setAutoScale(fcol.isAutoScaled());
col.setThreshold(fcol.getThreshold());
col.setColourByLabel(fcol.isColourByLabel());
- col.setThreshType(fcol.isAboveThreshold() ? "ABOVE" : (fcol
- .isBelowThreshold() ? "BELOW" : "NONE"));
+ col.setThreshType(fcol.isAboveThreshold() ? "ABOVE"
+ : (fcol.isBelowThreshold() ? "BELOW" : "NONE"));
}
ucs.addColour(col);
}
{
if (fr.setFeaturePriority(data, visibleNew))
{
- af.alignPanel.paintAlignment(true);
+ af.alignPanel.paintAlignment(true, true);
}
}
settingsPane.setLayout(borderLayout2);
dasSettingsPane.setLayout(borderLayout3);
bigPanel.setLayout(borderLayout4);
+
+ groupPanel = new JPanel();
+ bigPanel.add(groupPanel, BorderLayout.NORTH);
+
invert.setFont(JvSwingUtils.getLabelFont());
invert.setText(MessageManager.getString("label.invert_selection"));
invert.addActionListener(new ActionListener()
}
});
sortByDens.setFont(JvSwingUtils.getLabelFont());
- sortByDens.setText(MessageManager
- .getString("label.sequence_sort_by_density"));
+ sortByDens.setText(
+ MessageManager.getString("label.sequence_sort_by_density"));
sortByDens.addActionListener(new ActionListener()
{
@Override
@Override
public void stateChanged(ChangeEvent evt)
{
- fr.setTransparency((100 - transparency.getValue()) / 100f);
- af.alignPanel.paintAlignment(true);
+ if (!inConstruction)
+ {
+ fr.setTransparency((100 - transparency.getValue()) / 100f);
+ af.alignPanel.paintAlignment(true,true);
+ }
}
});
transparency.setMaximum(70);
- transparency.setToolTipText(MessageManager
- .getString("label.transparency_tip"));
+ transparency.setToolTipText(
+ MessageManager.getString("label.transparency_tip"));
fetchDAS.setText(MessageManager.getString("label.fetch_das_features"));
fetchDAS.addActionListener(new ActionListener()
{
public void noDasSourceActive()
{
complete();
- JvOptionPane
- .showInternalConfirmDialog(
- Desktop.desktop,
- MessageManager
- .getString("label.no_das_sources_selected_warn"),
- MessageManager
- .getString("label.no_das_sources_selected_title"),
- JvOptionPane.DEFAULT_OPTION,
- JvOptionPane.INFORMATION_MESSAGE);
+ JvOptionPane.showInternalConfirmDialog(Desktop.desktop,
+ MessageManager.getString("label.no_das_sources_selected_warn"),
+ MessageManager.getString("label.no_das_sources_selected_title"),
+ JvOptionPane.DEFAULT_OPTION, JvOptionPane.INFORMATION_MESSAGE);
}
// ///////////////////////////////////////////////////////////////////////
}
@Override
- public Component getTableCellRendererComponent(JTable tbl,
- Object color, boolean isSelected, boolean hasFocus, int row,
- int column)
+ public Component getTableCellRendererComponent(JTable tbl, Object color,
+ boolean isSelected, boolean hasFocus, int row, int column)
{
FeatureColourI cellColour = (FeatureColourI) color;
// JLabel comp = new JLabel();
}
}
-class ColorEditor extends AbstractCellEditor implements TableCellEditor,
- ActionListener
+class ColorEditor extends AbstractCellEditor
+ implements TableCellEditor, ActionListener
{
FeatureSettings me;