2 * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
3 * Copyright (C) $$Year-Rel$$ The Jalview Authors
5 * This file is part of Jalview.
7 * Jalview is free software: you can redistribute it and/or
8 * modify it under the terms of the GNU General Public License
9 * as published by the Free Software Foundation, either version 3
10 * of the License, or (at your option) any later version.
12 * Jalview is distributed in the hope that it will be useful, but
13 * WITHOUT ANY WARRANTY; without even the implied warranty
14 * of MERCHANTABILITY or FITNESS FOR A PARTICULAR
15 * PURPOSE. See the GNU General Public License for more details.
17 * You should have received a copy of the GNU General Public License
18 * along with Jalview. If not, see <http://www.gnu.org/licenses/>.
19 * The Jalview Authors are detailed in the 'AUTHORS' file.
23 import jalview.api.FeatureColourI;
24 import jalview.api.FeatureSettingsControllerI;
25 import jalview.bin.Cache;
26 import jalview.datamodel.AlignmentI;
27 import jalview.datamodel.SequenceI;
28 import jalview.datamodel.features.FeatureMatcher;
29 import jalview.datamodel.features.FeatureMatcherI;
30 import jalview.datamodel.features.FeatureMatcherSet;
31 import jalview.datamodel.features.FeatureMatcherSetI;
32 import jalview.gui.Help.HelpId;
33 import jalview.io.JalviewFileChooser;
34 import jalview.io.JalviewFileView;
35 import jalview.schemabinding.version2.CompoundMatcher;
36 import jalview.schemabinding.version2.Filter;
37 import jalview.schemabinding.version2.JalviewUserColours;
38 import jalview.schemabinding.version2.MatchCondition;
39 import jalview.schemabinding.version2.MatcherSet;
40 import jalview.schemabinding.version2.types.ColourNoValueColourType;
41 import jalview.schemabinding.version2.types.ColourThreshTypeType;
42 import jalview.schemabinding.version2.types.FeatureMatcherByType;
43 import jalview.schemes.FeatureColour;
44 import jalview.util.Format;
45 import jalview.util.MessageManager;
46 import jalview.util.Platform;
47 import jalview.util.matcher.Condition;
48 import jalview.viewmodel.AlignmentViewport;
49 import jalview.viewmodel.seqfeatures.FeatureRendererModel.FeatureSettingsBean;
50 import jalview.ws.DasSequenceFeatureFetcher;
51 import jalview.ws.dbsources.das.api.jalviewSourceI;
53 import java.awt.BorderLayout;
54 import java.awt.Color;
55 import java.awt.Component;
56 import java.awt.Dimension;
58 import java.awt.Graphics;
59 import java.awt.GridLayout;
60 import java.awt.Point;
61 import java.awt.Rectangle;
62 import java.awt.event.ActionEvent;
63 import java.awt.event.ActionListener;
64 import java.awt.event.ItemEvent;
65 import java.awt.event.ItemListener;
66 import java.awt.event.MouseAdapter;
67 import java.awt.event.MouseEvent;
68 import java.awt.event.MouseMotionAdapter;
69 import java.beans.PropertyChangeEvent;
70 import java.beans.PropertyChangeListener;
72 import java.io.FileInputStream;
73 import java.io.FileOutputStream;
74 import java.io.InputStreamReader;
75 import java.io.OutputStreamWriter;
76 import java.io.PrintWriter;
77 import java.util.Arrays;
78 import java.util.Collections;
79 import java.util.Comparator;
80 import java.util.HashMap;
81 import java.util.HashSet;
82 import java.util.Hashtable;
83 import java.util.Iterator;
84 import java.util.List;
87 import java.util.Vector;
89 import javax.help.HelpSetException;
90 import javax.swing.AbstractCellEditor;
91 import javax.swing.BorderFactory;
92 import javax.swing.Icon;
93 import javax.swing.JButton;
94 import javax.swing.JCheckBox;
95 import javax.swing.JCheckBoxMenuItem;
96 import javax.swing.JColorChooser;
97 import javax.swing.JDialog;
98 import javax.swing.JInternalFrame;
99 import javax.swing.JLabel;
100 import javax.swing.JLayeredPane;
101 import javax.swing.JMenuItem;
102 import javax.swing.JPanel;
103 import javax.swing.JPopupMenu;
104 import javax.swing.JScrollPane;
105 import javax.swing.JSlider;
106 import javax.swing.JTable;
107 import javax.swing.ListSelectionModel;
108 import javax.swing.SwingConstants;
109 import javax.swing.SwingUtilities;
110 import javax.swing.event.ChangeEvent;
111 import javax.swing.event.ChangeListener;
112 import javax.swing.table.AbstractTableModel;
113 import javax.swing.table.TableCellEditor;
114 import javax.swing.table.TableCellRenderer;
115 import javax.swing.table.TableColumn;
117 public class FeatureSettings extends JPanel
118 implements FeatureSettingsControllerI
121 * column indices of fields in Feature Settings table
123 static final int TYPE_COLUMN = 0;
125 static final int COLOUR_COLUMN = 1;
127 static final int FILTER_COLUMN = 2;
129 static final int SHOW_COLUMN = 3;
131 private static final int COLUMN_COUNT = 4;
133 private static final int MIN_WIDTH = 400;
135 private static final int MIN_HEIGHT = 400;
137 DasSourceBrowser dassourceBrowser;
139 DasSequenceFeatureFetcher dasFeatureFetcher;
141 JPanel dasSettingsPane = new JPanel();
143 final FeatureRenderer fr;
145 public final AlignFrame af;
148 * 'original' fields hold settings to restore on Cancel
150 Object[][] originalData;
152 private float originalTransparency;
154 private Map<String, FeatureMatcherSetI> originalFilters;
156 final JInternalFrame frame;
158 JScrollPane scrollPane = new JScrollPane();
164 JSlider transparency = new JSlider();
167 * when true, constructor is still executing - so ignore UI events
169 protected volatile boolean inConstruction = true;
171 int selectedRow = -1;
173 JButton fetchDAS = new JButton();
175 JButton saveDAS = new JButton();
177 JButton cancelDAS = new JButton();
179 boolean resettingTable = false;
182 * true when Feature Settings are updating from feature renderer
184 private boolean handlingUpdate = false;
187 * holds {featureCount, totalExtent} for each feature type
189 Map<String, float[]> typeWidth = null;
192 * Populates an XML model of the feature colour scheme for one feature type
198 protected static jalview.schemabinding.version2.Colour marshalColour(
199 String featureType, FeatureColourI fcol)
201 jalview.schemabinding.version2.Colour col = new jalview.schemabinding.version2.Colour();
202 if (fcol.isSimpleColour())
204 col.setRGB(Format.getHexString(fcol.getColour()));
208 col.setRGB(Format.getHexString(fcol.getMaxColour()));
209 col.setMin(fcol.getMin());
210 col.setMax(fcol.getMax());
211 col.setMinRGB(jalview.util.Format.getHexString(fcol.getMinColour()));
212 col.setAutoScale(fcol.isAutoScaled());
213 col.setThreshold(fcol.getThreshold());
214 col.setColourByLabel(fcol.isColourByLabel());
215 col.setThreshType(fcol.isAboveThreshold() ? ColourThreshTypeType.ABOVE
216 : (fcol.isBelowThreshold() ? ColourThreshTypeType.BELOW
217 : ColourThreshTypeType.NONE));
218 if (fcol.isColourByAttribute())
220 col.setAttributeName(fcol.getAttributeName());
222 Color noColour = fcol.getNoColour();
223 if (noColour == null)
225 col.setNoValueColour(ColourNoValueColourType.NONE);
227 else if (noColour == fcol.getMaxColour())
229 col.setNoValueColour(ColourNoValueColourType.MAX);
233 col.setNoValueColour(ColourNoValueColourType.MIN);
236 col.setName(featureType);
241 * Populates an XML model of the feature filter(s) for one feature type
243 * @param firstMatcher
244 * the first (or only) match condition)
246 * remaining match conditions (if any)
248 * if true, conditions are and-ed, else or-ed
250 protected static MatcherSet marshalFilter(FeatureMatcherI firstMatcher,
251 Iterator<FeatureMatcherI> filters, boolean and)
253 MatcherSet result = new MatcherSet();
255 if (filters.hasNext())
260 CompoundMatcher compound = new CompoundMatcher();
261 compound.setAnd(and);
262 MatcherSet matcher1 = marshalFilter(firstMatcher,
263 Collections.emptyIterator(), and);
264 compound.addMatcherSet(matcher1);
265 FeatureMatcherI nextMatcher = filters.next();
266 MatcherSet matcher2 = marshalFilter(nextMatcher, filters, and);
267 compound.addMatcherSet(matcher2);
268 result.setCompoundMatcher(compound);
273 * single condition matcher
275 MatchCondition matcherModel = new MatchCondition();
276 matcherModel.setCondition(
277 firstMatcher.getMatcher().getCondition().getStableName());
278 matcherModel.setValue(firstMatcher.getMatcher().getPattern());
279 if (firstMatcher.isByAttribute())
281 matcherModel.setBy(FeatureMatcherByType.BYATTRIBUTE);
282 matcherModel.setAttributeName(firstMatcher.getAttribute());
284 else if (firstMatcher.isByLabel())
286 matcherModel.setBy(FeatureMatcherByType.BYLABEL);
288 else if (firstMatcher.isByScore())
290 matcherModel.setBy(FeatureMatcherByType.BYSCORE);
292 result.setMatchCondition(matcherModel);
299 * Loads one XML model of a feature filter to a Jalview object
304 protected static FeatureMatcherSetI unmarshalFilter(Filter filterModel)
306 FeatureMatcherSetI result = new FeatureMatcherSet();
307 MatcherSet matcherSetModel = filterModel.getMatcherSet();
310 unmarshalFilterConditions(result, matcherSetModel, true);
311 } catch (IllegalStateException e)
313 // mixing AND and OR conditions perhaps
315 String.format("Error reading filter conditions for '%s': %s",
316 filterModel.getFeatureType(), e.getMessage()));
317 // return as much as was parsed up to the error
324 * Adds feature match conditions to matcherSet as unmarshalled from XML
325 * (possibly recursively for compound conditions)
328 * @param matcherSetModel
330 * if true, multiple conditions are AND-ed, else they are OR-ed
331 * @throws IllegalStateException
332 * if AND and OR conditions are mixed
334 protected static void unmarshalFilterConditions(
335 FeatureMatcherSetI matcherSet, MatcherSet matcherSetModel,
338 MatchCondition mc = matcherSetModel.getMatchCondition();
344 FeatureMatcherByType filterBy = mc.getBy();
345 Condition cond = Condition.fromString(mc.getCondition());
346 String pattern = mc.getValue();
347 FeatureMatcherI matchCondition = null;
348 if (filterBy == FeatureMatcherByType.BYLABEL)
350 matchCondition = FeatureMatcher.byLabel(cond, pattern);
352 else if (filterBy == FeatureMatcherByType.BYSCORE)
354 matchCondition = FeatureMatcher.byScore(cond, pattern);
357 else if (filterBy == FeatureMatcherByType.BYATTRIBUTE)
359 String[] attNames = mc.getAttributeName();
360 matchCondition = FeatureMatcher.byAttribute(cond, pattern,
365 * note this throws IllegalStateException if AND-ing to a
366 * previously OR-ed compound condition, or vice versa
370 matcherSet.and(matchCondition);
374 matcherSet.or(matchCondition);
382 MatcherSet[] matchers = matcherSetModel.getCompoundMatcher()
384 boolean anded = matcherSetModel.getCompoundMatcher().getAnd();
385 if (matchers.length == 2)
387 unmarshalFilterConditions(matcherSet, matchers[0], anded);
388 unmarshalFilterConditions(matcherSet, matchers[1], anded);
392 System.err.println("Malformed compound filter condition");
398 * Loads one XML model of a feature colour to a Jalview object
403 protected static FeatureColourI unmarshalColour(
404 jalview.schemabinding.version2.Colour colourModel)
406 FeatureColourI colour = null;
408 if (colourModel.hasMax())
412 Color noValueColour = null;
416 mincol = new Color(Integer.parseInt(colourModel.getMinRGB(), 16));
417 maxcol = new Color(Integer.parseInt(colourModel.getRGB(), 16));
418 } catch (Exception e)
420 Cache.log.warn("Couldn't parse out graduated feature color.", e);
423 ColourNoValueColourType noCol = colourModel.getNoValueColour();
424 if (noCol == ColourNoValueColourType.MIN)
426 noValueColour = mincol;
428 else if (noCol == ColourNoValueColourType.MAX)
430 noValueColour = maxcol;
433 colour = new FeatureColour(mincol, maxcol, noValueColour,
434 colourModel.getMin(),
435 colourModel.getMax());
436 String[] attributes = colourModel.getAttributeName();
437 if (attributes != null && attributes.length > 0)
439 colour.setAttributeName(attributes);
441 if (colourModel.hasAutoScale())
443 colour.setAutoScaled(colourModel.getAutoScale());
445 if (colourModel.hasColourByLabel())
447 colour.setColourByLabel(colourModel.getColourByLabel());
449 if (colourModel.hasThreshold())
451 colour.setThreshold(colourModel.getThreshold());
453 ColourThreshTypeType ttyp = colourModel.getThreshType();
456 if (ttyp == ColourThreshTypeType.ABOVE)
458 colour.setAboveThreshold(true);
460 else if (ttyp == ColourThreshTypeType.BELOW)
462 colour.setBelowThreshold(true);
468 Color color = new Color(Integer.parseInt(colourModel.getRGB(), 16));
469 colour = new FeatureColour(color);
480 public FeatureSettings(AlignFrame alignFrame)
482 this.af = alignFrame;
483 fr = af.getFeatureRenderer();
485 // save transparency for restore on Cancel
486 originalTransparency = fr.getTransparency();
487 int originalTransparencyAsPercent = (int) (originalTransparency * 100);
488 transparency.setMaximum(100 - originalTransparencyAsPercent);
490 originalFilters = new HashMap<>(fr.getFeatureFilters()); // shallow copy
495 } catch (Exception ex)
497 ex.printStackTrace();
503 public String getToolTipText(MouseEvent e)
506 int column = table.columnAtPoint(e.getPoint());
510 tip = JvSwingUtils.wrapTooltip(true, MessageManager
511 .getString("label.feature_settings_click_drag"));
514 int row = table.rowAtPoint(e.getPoint());
515 FeatureMatcherSet o = (FeatureMatcherSet) table.getValueAt(row,
518 ? MessageManager.getString("label.filters_tooltip")
527 table.getTableHeader().setFont(new Font("Verdana", Font.PLAIN, 12));
528 table.setFont(new Font("Verdana", Font.PLAIN, 12));
530 // table.setDefaultRenderer(Color.class, new ColorRenderer());
531 // table.setDefaultEditor(Color.class, new ColorEditor(this));
533 table.setDefaultEditor(FeatureColour.class, new ColorEditor(this));
534 table.setDefaultRenderer(FeatureColour.class, new ColorRenderer());
536 table.setDefaultEditor(FeatureMatcherSet.class, new FilterEditor(this));
537 table.setDefaultRenderer(FeatureMatcherSet.class, new FilterRenderer());
539 TableColumn colourColumn = new TableColumn(COLOUR_COLUMN, 75,
540 new ColorRenderer(), new ColorEditor(this));
541 table.addColumn(colourColumn);
543 TableColumn filterColumn = new TableColumn(FILTER_COLUMN, 75,
544 new FilterRenderer(), new FilterEditor(this));
545 table.addColumn(filterColumn);
547 table.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
549 table.addMouseListener(new MouseAdapter()
552 public void mousePressed(MouseEvent evt)
554 selectedRow = table.rowAtPoint(evt.getPoint());
555 String type = (String) table.getValueAt(selectedRow, TYPE_COLUMN);
556 if (evt.isPopupTrigger())
558 Object colour = table.getValueAt(selectedRow, COLOUR_COLUMN);
559 popupSort(selectedRow, type, colour, fr.getMinMax(), evt.getX(),
562 else if (evt.getClickCount() == 2)
564 boolean invertSelection = evt.isAltDown();
565 boolean toggleSelection = Platform.isControlDown(evt);
566 boolean extendSelection = evt.isShiftDown();
567 fr.ap.alignFrame.avc.markColumnsContainingFeatures(
568 invertSelection, extendSelection, toggleSelection, type);
572 // isPopupTrigger fires on mouseReleased on Windows
574 public void mouseReleased(MouseEvent evt)
576 selectedRow = table.rowAtPoint(evt.getPoint());
577 if (evt.isPopupTrigger())
579 String type = (String) table.getValueAt(selectedRow, TYPE_COLUMN);
580 Object colour = table.getValueAt(selectedRow, COLOUR_COLUMN);
581 popupSort(selectedRow, type, colour, fr.getMinMax(), evt.getX(),
587 table.addMouseMotionListener(new MouseMotionAdapter()
590 public void mouseDragged(MouseEvent evt)
592 int newRow = table.rowAtPoint(evt.getPoint());
593 if (newRow != selectedRow && selectedRow != -1 && newRow != -1)
596 * reposition 'selectedRow' to 'newRow' (the dragged to location)
597 * this could be more than one row away for a very fast drag action
598 * so just swap it with adjacent rows until we get it there
600 Object[][] data = ((FeatureTableModel) table.getModel())
602 int direction = newRow < selectedRow ? -1 : 1;
603 for (int i = selectedRow; i != newRow; i += direction)
605 Object[] temp = data[i];
606 data[i] = data[i + direction];
607 data[i + direction] = temp;
609 updateFeatureRenderer(data);
611 selectedRow = newRow;
615 // table.setToolTipText(JvSwingUtils.wrapTooltip(true,
616 // MessageManager.getString("label.feature_settings_click_drag")));
617 scrollPane.setViewportView(table);
619 dassourceBrowser = new DasSourceBrowser(this);
620 dasSettingsPane.add(dassourceBrowser, BorderLayout.CENTER);
622 if (af.getViewport().isShowSequenceFeatures() || !fr.hasRenderOrder())
624 fr.findAllFeatures(true); // display everything!
627 discoverAllFeatureData();
628 final PropertyChangeListener change;
629 final FeatureSettings fs = this;
630 fr.addPropertyChangeListener(change = new PropertyChangeListener()
633 public void propertyChange(PropertyChangeEvent evt)
635 if (!fs.resettingTable && !fs.handlingUpdate)
637 fs.handlingUpdate = true;
639 // new groups may be added with new sequence feature types only
640 fs.handlingUpdate = false;
646 frame = new JInternalFrame();
647 frame.setContentPane(this);
648 if (Platform.isAMac())
650 Desktop.addInternalFrame(frame,
651 MessageManager.getString("label.sequence_feature_settings"),
656 Desktop.addInternalFrame(frame,
657 MessageManager.getString("label.sequence_feature_settings"),
660 frame.setMinimumSize(new Dimension(MIN_WIDTH, MIN_HEIGHT));
662 frame.addInternalFrameListener(
663 new javax.swing.event.InternalFrameAdapter()
666 public void internalFrameClosed(
667 javax.swing.event.InternalFrameEvent evt)
669 fr.removePropertyChangeListener(change);
670 dassourceBrowser.fs = null;
673 frame.setLayer(JLayeredPane.PALETTE_LAYER);
674 inConstruction = false;
677 protected void popupSort(final int rowSelected, final String type,
678 final Object typeCol, final Map<String, float[][]> minmax, int x,
681 final FeatureColourI featureColour = (FeatureColourI) typeCol;
683 JPopupMenu men = new JPopupMenu(MessageManager
684 .formatMessage("label.settings_for_param", new String[]
686 JMenuItem scr = new JMenuItem(
687 MessageManager.getString("label.sort_by_score"));
689 final FeatureSettings me = this;
690 scr.addActionListener(new ActionListener()
694 public void actionPerformed(ActionEvent e)
697 .sortAlignmentByFeatureScore(Arrays.asList(new String[]
702 JMenuItem dens = new JMenuItem(
703 MessageManager.getString("label.sort_by_density"));
704 dens.addActionListener(new ActionListener()
708 public void actionPerformed(ActionEvent e)
711 .sortAlignmentByFeatureDensity(Arrays.asList(new String[]
719 * variable colour options include colour by label, by score,
720 * by selected attribute text, or attribute value
722 final JCheckBoxMenuItem mxcol = new JCheckBoxMenuItem(
723 MessageManager.getString("label.variable_colour"));
724 mxcol.setSelected(!featureColour.isSimpleColour());
726 mxcol.addActionListener(new ActionListener()
728 JColorChooser colorChooser;
731 public void actionPerformed(ActionEvent e)
733 if (e.getSource() == mxcol)
735 if (featureColour.isSimpleColour())
737 FeatureTypeSettings fc = new FeatureTypeSettings(me.fr, type);
738 fc.addActionListener(this);
742 // bring up simple color chooser
743 colorChooser = new JColorChooser();
744 String title = MessageManager
745 .getString("label.select_colour");
746 JDialog dialog = JColorChooser.createDialog(me,
747 title, true, // modal
748 colorChooser, this, // OK button handler
749 null); // no CANCEL button handler
750 colorChooser.setColor(featureColour.getMaxColour());
751 dialog.setVisible(true);
756 if (e.getSource() instanceof FeatureTypeSettings)
759 * update after OK in feature colour dialog; the updated
760 * colour will have already been set in the FeatureRenderer
762 FeatureColourI fci = fr.getFeatureColours().get(type);
763 table.setValueAt(fci, rowSelected, 1);
768 // probably the color chooser!
769 table.setValueAt(new FeatureColour(colorChooser.getColor()),
772 me.updateFeatureRenderer(
773 ((FeatureTableModel) table.getModel()).getData(),
781 JMenuItem selCols = new JMenuItem(
782 MessageManager.getString("label.select_columns_containing"));
783 selCols.addActionListener(new ActionListener()
786 public void actionPerformed(ActionEvent arg0)
788 fr.ap.alignFrame.avc.markColumnsContainingFeatures(false, false,
792 JMenuItem clearCols = new JMenuItem(MessageManager
793 .getString("label.select_columns_not_containing"));
794 clearCols.addActionListener(new ActionListener()
797 public void actionPerformed(ActionEvent arg0)
799 fr.ap.alignFrame.avc.markColumnsContainingFeatures(true, false,
803 JMenuItem hideCols = new JMenuItem(
804 MessageManager.getString("label.hide_columns_containing"));
805 hideCols.addActionListener(new ActionListener()
808 public void actionPerformed(ActionEvent arg0)
810 fr.ap.alignFrame.hideFeatureColumns(type, true);
813 JMenuItem hideOtherCols = new JMenuItem(
814 MessageManager.getString("label.hide_columns_not_containing"));
815 hideOtherCols.addActionListener(new ActionListener()
818 public void actionPerformed(ActionEvent arg0)
820 fr.ap.alignFrame.hideFeatureColumns(type, false);
826 men.add(hideOtherCols);
827 men.show(table, x, y);
831 synchronized public void discoverAllFeatureData()
833 Set<String> allGroups = new HashSet<>();
834 AlignmentI alignment = af.getViewport().getAlignment();
836 for (int i = 0; i < alignment.getHeight(); i++)
838 SequenceI seq = alignment.getSequenceAt(i);
839 for (String group : seq.getFeatures().getFeatureGroups(true))
841 if (group != null && !allGroups.contains(group))
843 allGroups.add(group);
844 checkGroupState(group);
855 * Synchronise gui group list and check visibility of group
858 * @return true if group is visible
860 private boolean checkGroupState(String group)
862 boolean visible = fr.checkGroupVisibility(group, true);
864 for (int g = 0; g < groupPanel.getComponentCount(); g++)
866 if (((JCheckBox) groupPanel.getComponent(g)).getText().equals(group))
868 ((JCheckBox) groupPanel.getComponent(g)).setSelected(visible);
873 final String grp = group;
874 final JCheckBox check = new JCheckBox(group, visible);
875 check.setFont(new Font("Serif", Font.BOLD, 12));
876 check.setToolTipText(group);
877 check.addItemListener(new ItemListener()
880 public void itemStateChanged(ItemEvent evt)
882 fr.setGroupVisibility(check.getText(), check.isSelected());
883 resetTable(new String[] { grp });
884 af.alignPanel.paintAlignment(true, true);
887 groupPanel.add(check);
891 synchronized void resetTable(String[] groupChanged)
897 resettingTable = true;
898 typeWidth = new Hashtable<>();
899 // TODO: change avWidth calculation to 'per-sequence' average and use long
902 Set<String> displayableTypes = new HashSet<>();
903 Set<String> foundGroups = new HashSet<>();
906 * determine which feature types may be visible depending on
907 * which groups are selected, and recompute average width data
909 for (int i = 0; i < af.getViewport().getAlignment().getHeight(); i++)
912 SequenceI seq = af.getViewport().getAlignment().getSequenceAt(i);
915 * get the sequence's groups for positional features
916 * and keep track of which groups are visible
918 Set<String> groups = seq.getFeatures().getFeatureGroups(true);
919 Set<String> visibleGroups = new HashSet<>();
920 for (String group : groups)
922 if (group == null || checkGroupState(group))
924 visibleGroups.add(group);
927 foundGroups.addAll(groups);
930 * get distinct feature types for visible groups
931 * record distinct visible types, and their count and total length
933 Set<String> types = seq.getFeatures().getFeatureTypesForGroups(true,
934 visibleGroups.toArray(new String[visibleGroups.size()]));
935 for (String type : types)
937 displayableTypes.add(type);
938 float[] avWidth = typeWidth.get(type);
941 avWidth = new float[2];
942 typeWidth.put(type, avWidth);
944 // todo this could include features with a non-visible group
945 // - do we greatly care?
946 // todo should we include non-displayable features here, and only
947 // update when features are added?
948 avWidth[0] += seq.getFeatures().getFeatureCount(true, type);
949 avWidth[1] += seq.getFeatures().getTotalFeatureLength(type);
953 Object[][] data = new Object[displayableTypes.size()][COLUMN_COUNT];
956 if (fr.hasRenderOrder())
960 fr.findAllFeatures(groupChanged != null); // prod to update
961 // colourschemes. but don't
963 // First add the checks in the previous render order,
964 // in case the window has been closed and reopened
966 List<String> frl = fr.getRenderOrder();
967 for (int ro = frl.size() - 1; ro > -1; ro--)
969 String type = frl.get(ro);
971 if (!displayableTypes.contains(type))
976 data[dataIndex][TYPE_COLUMN] = type;
977 data[dataIndex][COLOUR_COLUMN] = fr.getFeatureStyle(type);
978 FeatureMatcherSetI featureFilter = fr.getFeatureFilter(type);
979 data[dataIndex][FILTER_COLUMN] = featureFilter == null
980 ? new FeatureMatcherSet()
982 data[dataIndex][SHOW_COLUMN] = new Boolean(
983 af.getViewport().getFeaturesDisplayed().isVisible(type));
985 displayableTypes.remove(type);
990 * process any extra features belonging only to
991 * a group which was just selected
993 while (!displayableTypes.isEmpty())
995 String type = displayableTypes.iterator().next();
996 data[dataIndex][TYPE_COLUMN] = type;
998 data[dataIndex][COLOUR_COLUMN] = fr.getFeatureStyle(type);
999 if (data[dataIndex][COLOUR_COLUMN] == null)
1001 // "Colour has been updated in another view!!"
1002 fr.clearRenderOrder();
1005 FeatureMatcherSetI featureFilter = fr.getFeatureFilter(type);
1006 data[dataIndex][FILTER_COLUMN] = featureFilter == null
1007 ? new FeatureMatcherSet()
1009 data[dataIndex][SHOW_COLUMN] = new Boolean(true);
1011 displayableTypes.remove(type);
1014 if (originalData == null)
1016 originalData = new Object[data.length][COLUMN_COUNT];
1017 for (int i = 0; i < data.length; i++)
1019 System.arraycopy(data[i], 0, originalData[i], 0, COLUMN_COUNT);
1024 updateOriginalData(data);
1027 table.setModel(new FeatureTableModel(data));
1028 table.getColumnModel().getColumn(0).setPreferredWidth(200);
1030 groupPanel.setLayout(
1031 new GridLayout(fr.getFeatureGroupsSize() / 4 + 1, 4));
1032 pruneGroups(foundGroups);
1033 groupPanel.validate();
1035 updateFeatureRenderer(data, groupChanged != null);
1036 resettingTable = false;
1040 * Updates 'originalData' (used for restore on Cancel) if we detect that changes
1041 * have been made outwith this dialog
1043 * <li>a new feature type added (and made visible)</li>
1044 * <li>a feature colour changed (in the Amend Features dialog)</li>
1049 protected void updateOriginalData(Object[][] foundData)
1051 // todo LinkedHashMap instead of Object[][] would be nice
1053 Object[][] currentData = ((FeatureTableModel) table.getModel())
1055 for (Object[] row : foundData)
1057 String type = (String) row[TYPE_COLUMN];
1058 boolean found = false;
1059 for (Object[] current : currentData)
1061 if (type.equals(current[TYPE_COLUMN]))
1065 * currently dependent on object equality here;
1066 * really need an equals method on FeatureColour
1068 if (!row[COLOUR_COLUMN].equals(current[COLOUR_COLUMN]))
1071 * feature colour has changed externally - update originalData
1073 for (Object[] original : originalData)
1075 if (type.equals(original[TYPE_COLUMN]))
1077 original[COLOUR_COLUMN] = row[COLOUR_COLUMN];
1088 * new feature detected - add to original data (on top)
1090 Object[][] newData = new Object[originalData.length
1092 for (int i = 0; i < originalData.length; i++)
1094 System.arraycopy(originalData[i], 0, newData[i + 1], 0,
1098 originalData = newData;
1104 * Remove from the groups panel any checkboxes for groups that are not in the
1105 * foundGroups set. This enables removing a group from the display when the last
1106 * feature in that group is deleted.
1108 * @param foundGroups
1110 protected void pruneGroups(Set<String> foundGroups)
1112 for (int g = 0; g < groupPanel.getComponentCount(); g++)
1114 JCheckBox checkbox = (JCheckBox) groupPanel.getComponent(g);
1115 if (!foundGroups.contains(checkbox.getText()))
1117 groupPanel.remove(checkbox);
1123 * reorder data based on the featureRenderers global priority list.
1127 private void ensureOrder(Object[][] data)
1129 boolean sort = false;
1130 float[] order = new float[data.length];
1131 for (int i = 0; i < order.length; i++)
1133 order[i] = fr.getOrder(data[i][0].toString());
1136 order[i] = fr.setOrder(data[i][0].toString(), i / order.length);
1140 sort = sort || order[i - 1] > order[i];
1145 jalview.util.QuickSort.sort(order, data);
1150 * Offers a file chooser dialog, and then loads the feature colours and
1151 * filters from file in XML format and unmarshals to Jalview feature settings
1155 JalviewFileChooser chooser = new JalviewFileChooser("fc",
1156 "Sequence Feature Colours");
1157 chooser.setFileView(new JalviewFileView());
1158 chooser.setDialogTitle(
1159 MessageManager.getString("label.load_feature_colours"));
1160 chooser.setToolTipText(MessageManager.getString("action.load"));
1162 int value = chooser.showOpenDialog(this);
1164 if (value == JalviewFileChooser.APPROVE_OPTION)
1166 File file = chooser.getSelectedFile();
1170 InputStreamReader in = new InputStreamReader(
1171 new FileInputStream(file), "UTF-8");
1173 JalviewUserColours jucs = JalviewUserColours.unmarshal(in);
1176 * load feature colours
1178 for (int i = jucs.getColourCount() - 1; i >= 0; i--)
1180 jalview.schemabinding.version2.Colour newcol = jucs.getColour(i);
1181 FeatureColourI colour = unmarshalColour(newcol);
1182 fr.setColour(newcol.getName(), colour);
1183 fr.setOrder(newcol.getName(), i / (float) jucs.getColourCount());
1187 * load feature filters; loaded filters will replace any that are
1188 * currently defined, other defined filters are left unchanged
1190 for (int i = 0; i < jucs.getFilterCount(); i++)
1192 jalview.schemabinding.version2.Filter filterModel = jucs
1194 FeatureMatcherSetI filter = unmarshalFilter(filterModel);
1195 if (!filter.isEmpty())
1197 fr.setFeatureFilter(filterModel.getFeatureType(), filter);
1202 * update feature settings table
1207 Object[][] data = ((FeatureTableModel) table.getModel())
1210 updateFeatureRenderer(data, false);
1213 } catch (Exception ex)
1215 System.out.println("Error loading User Colour File\n" + ex);
1221 * Offers a file chooser dialog, and then saves the current feature colours
1222 * and any filters to the selected file in XML format
1226 JalviewFileChooser chooser = new JalviewFileChooser("fc",
1227 "Sequence Feature Colours");
1228 chooser.setFileView(new JalviewFileView());
1229 chooser.setDialogTitle(
1230 MessageManager.getString("label.save_feature_colours"));
1231 chooser.setToolTipText(MessageManager.getString("action.save"));
1233 int value = chooser.showSaveDialog(this);
1235 if (value == JalviewFileChooser.APPROVE_OPTION)
1237 String choice = chooser.getSelectedFile().getPath();
1238 JalviewUserColours ucs = new JalviewUserColours();
1239 ucs.setSchemeName("Sequence Features");
1242 PrintWriter out = new PrintWriter(new OutputStreamWriter(
1243 new FileOutputStream(choice), "UTF-8"));
1246 * sort feature types by colour order, from 0 (highest)
1249 Set<String> fr_colours = fr.getAllFeatureColours();
1250 String[] sortedTypes = fr_colours
1251 .toArray(new String[fr_colours.size()]);
1252 Arrays.sort(sortedTypes, new Comparator<String>()
1255 public int compare(String type1, String type2)
1257 return Float.compare(fr.getOrder(type1), fr.getOrder(type2));
1262 * save feature colours
1264 for (String featureType : sortedTypes)
1266 FeatureColourI fcol = fr.getFeatureStyle(featureType);
1267 jalview.schemabinding.version2.Colour col = marshalColour(
1273 * save any feature filters
1275 for (String featureType : sortedTypes)
1277 FeatureMatcherSetI filter = fr.getFeatureFilter(featureType);
1278 if (filter != null && !filter.isEmpty())
1280 Iterator<FeatureMatcherI> iterator = filter.getMatchers().iterator();
1281 FeatureMatcherI firstMatcher = iterator.next();
1282 MatcherSet ms = marshalFilter(firstMatcher, iterator,
1284 Filter filterModel = new Filter();
1285 filterModel.setFeatureType(featureType);
1286 filterModel.setMatcherSet(ms);
1287 ucs.addFilter(filterModel);
1293 } catch (Exception ex)
1295 ex.printStackTrace();
1300 public void invertSelection()
1302 for (int i = 0; i < table.getRowCount(); i++)
1304 Boolean value = (Boolean) table.getValueAt(i, SHOW_COLUMN);
1306 table.setValueAt(new Boolean(!value.booleanValue()), i, SHOW_COLUMN);
1310 public void orderByAvWidth()
1312 if (table == null || table.getModel() == null)
1316 Object[][] data = ((FeatureTableModel) table.getModel()).getData();
1317 float[] width = new float[data.length];
1321 for (int i = 0; i < data.length; i++)
1323 awidth = typeWidth.get(data[i][TYPE_COLUMN]);
1326 width[i] = awidth[1] / awidth[0];// *awidth[0]*awidth[2]; - better
1327 // weight - but have to make per
1328 // sequence, too (awidth[2])
1329 // if (width[i]==1) // hack to distinguish single width sequences.
1340 boolean sort = false;
1341 for (int i = 0; i < width.length; i++)
1343 // awidth = (float[]) typeWidth.get(data[i][0]);
1346 width[i] = fr.getOrder(data[i][TYPE_COLUMN].toString());
1349 width[i] = fr.setOrder(data[i][TYPE_COLUMN].toString(),
1355 width[i] /= max; // normalize
1356 fr.setOrder(data[i][TYPE_COLUMN].toString(), width[i]); // store for later
1360 sort = sort || width[i - 1] > width[i];
1365 jalview.util.QuickSort.sort(width, data);
1366 // update global priority order
1369 updateFeatureRenderer(data, false);
1377 frame.setClosed(true);
1378 } catch (Exception exe)
1384 public void updateFeatureRenderer(Object[][] data)
1386 updateFeatureRenderer(data, true);
1390 * Update the priority order of features; only repaint if this changed the order
1391 * of visible features
1396 private void updateFeatureRenderer(Object[][] data, boolean visibleNew)
1398 FeatureSettingsBean[] rowData = getTableAsBeans(data);
1400 if (fr.setFeaturePriority(rowData, visibleNew))
1402 af.alignPanel.paintAlignment(true, true);
1407 * Converts table data into an array of data beans
1409 private FeatureSettingsBean[] getTableAsBeans(Object[][] data)
1411 FeatureSettingsBean[] rowData = new FeatureSettingsBean[data.length];
1412 for (int i = 0; i < data.length; i++)
1414 String type = (String) data[i][TYPE_COLUMN];
1415 FeatureColourI colour = (FeatureColourI) data[i][COLOUR_COLUMN];
1416 FeatureMatcherSetI theFilter = (FeatureMatcherSetI) data[i][FILTER_COLUMN];
1417 Boolean isShown = (Boolean) data[i][SHOW_COLUMN];
1418 rowData[i] = new FeatureSettingsBean(type, colour, theFilter,
1424 private void jbInit() throws Exception
1426 this.setLayout(new BorderLayout());
1428 JPanel settingsPane = new JPanel();
1429 settingsPane.setLayout(new BorderLayout());
1431 dasSettingsPane.setLayout(new BorderLayout());
1433 JPanel bigPanel = new JPanel();
1434 bigPanel.setLayout(new BorderLayout());
1436 groupPanel = new JPanel();
1437 bigPanel.add(groupPanel, BorderLayout.NORTH);
1439 JButton invert = new JButton(
1440 MessageManager.getString("label.invert_selection"));
1441 invert.setFont(JvSwingUtils.getLabelFont());
1442 invert.addActionListener(new ActionListener()
1445 public void actionPerformed(ActionEvent e)
1451 JButton optimizeOrder = new JButton(
1452 MessageManager.getString("label.optimise_order"));
1453 optimizeOrder.setFont(JvSwingUtils.getLabelFont());
1454 optimizeOrder.addActionListener(new ActionListener()
1457 public void actionPerformed(ActionEvent e)
1463 JButton sortByScore = new JButton(
1464 MessageManager.getString("label.seq_sort_by_score"));
1465 sortByScore.setFont(JvSwingUtils.getLabelFont());
1466 sortByScore.addActionListener(new ActionListener()
1469 public void actionPerformed(ActionEvent e)
1471 af.avc.sortAlignmentByFeatureScore(null);
1474 JButton sortByDens = new JButton(
1475 MessageManager.getString("label.sequence_sort_by_density"));
1476 sortByDens.setFont(JvSwingUtils.getLabelFont());
1477 sortByDens.addActionListener(new ActionListener()
1480 public void actionPerformed(ActionEvent e)
1482 af.avc.sortAlignmentByFeatureDensity(null);
1486 JButton help = new JButton(MessageManager.getString("action.help"));
1487 help.setFont(JvSwingUtils.getLabelFont());
1488 help.addActionListener(new ActionListener()
1491 public void actionPerformed(ActionEvent e)
1495 Help.showHelpWindow(HelpId.SequenceFeatureSettings);
1496 } catch (HelpSetException e1)
1498 e1.printStackTrace();
1502 help.setFont(JvSwingUtils.getLabelFont());
1503 help.setText(MessageManager.getString("action.help"));
1504 help.addActionListener(new ActionListener()
1507 public void actionPerformed(ActionEvent e)
1511 Help.showHelpWindow(HelpId.SequenceFeatureSettings);
1512 } catch (HelpSetException e1)
1514 e1.printStackTrace();
1519 JButton cancel = new JButton(MessageManager.getString("action.cancel"));
1520 cancel.setFont(JvSwingUtils.getLabelFont());
1521 cancel.addActionListener(new ActionListener()
1524 public void actionPerformed(ActionEvent e)
1526 fr.setTransparency(originalTransparency);
1527 fr.setFeatureFilters(originalFilters);
1528 updateFeatureRenderer(originalData);
1533 JButton ok = new JButton(MessageManager.getString("action.ok"));
1534 ok.setFont(JvSwingUtils.getLabelFont());
1535 ok.addActionListener(new ActionListener()
1538 public void actionPerformed(ActionEvent e)
1544 JButton loadColours = new JButton(
1545 MessageManager.getString("label.load_colours"));
1546 loadColours.setFont(JvSwingUtils.getLabelFont());
1547 loadColours.addActionListener(new ActionListener()
1550 public void actionPerformed(ActionEvent e)
1556 JButton saveColours = new JButton(
1557 MessageManager.getString("label.save_colours"));
1558 saveColours.setFont(JvSwingUtils.getLabelFont());
1559 saveColours.addActionListener(new ActionListener()
1562 public void actionPerformed(ActionEvent e)
1567 transparency.addChangeListener(new ChangeListener()
1570 public void stateChanged(ChangeEvent evt)
1572 if (!inConstruction)
1574 fr.setTransparency((100 - transparency.getValue()) / 100f);
1575 af.alignPanel.paintAlignment(true, true);
1580 transparency.setMaximum(70);
1581 transparency.setToolTipText(
1582 MessageManager.getString("label.transparency_tip"));
1583 fetchDAS.setText(MessageManager.getString("label.fetch_das_features"));
1584 fetchDAS.addActionListener(new ActionListener()
1587 public void actionPerformed(ActionEvent e)
1589 fetchDAS_actionPerformed(e);
1592 saveDAS.setText(MessageManager.getString("action.save_as_default"));
1593 saveDAS.addActionListener(new ActionListener()
1596 public void actionPerformed(ActionEvent e)
1598 saveDAS_actionPerformed(e);
1602 JPanel dasButtonPanel = new JPanel();
1603 dasButtonPanel.setBorder(BorderFactory.createEtchedBorder());
1604 dasSettingsPane.setBorder(null);
1605 cancelDAS.setEnabled(false);
1606 cancelDAS.setText(MessageManager.getString("action.cancel_fetch"));
1607 cancelDAS.addActionListener(new ActionListener()
1610 public void actionPerformed(ActionEvent e)
1612 cancelDAS_actionPerformed(e);
1616 JPanel transPanel = new JPanel(new GridLayout(1, 2));
1617 bigPanel.add(transPanel, BorderLayout.SOUTH);
1619 JPanel transbuttons = new JPanel(new GridLayout(5, 1));
1620 transbuttons.add(optimizeOrder);
1621 transbuttons.add(invert);
1622 transbuttons.add(sortByScore);
1623 transbuttons.add(sortByDens);
1624 transbuttons.add(help);
1625 transPanel.add(transparency);
1626 transPanel.add(transbuttons);
1628 JPanel buttonPanel = new JPanel();
1629 buttonPanel.add(ok);
1630 buttonPanel.add(cancel);
1631 buttonPanel.add(loadColours);
1632 buttonPanel.add(saveColours);
1633 bigPanel.add(scrollPane, BorderLayout.CENTER);
1634 dasSettingsPane.add(dasButtonPanel, BorderLayout.SOUTH);
1635 dasButtonPanel.add(fetchDAS);
1636 dasButtonPanel.add(cancelDAS);
1637 dasButtonPanel.add(saveDAS);
1638 settingsPane.add(bigPanel, BorderLayout.CENTER);
1639 settingsPane.add(buttonPanel, BorderLayout.SOUTH);
1640 this.add(settingsPane);
1643 public void fetchDAS_actionPerformed(ActionEvent e)
1645 fetchDAS.setEnabled(false);
1646 cancelDAS.setEnabled(true);
1647 dassourceBrowser.setGuiEnabled(false);
1648 Vector<jalviewSourceI> selectedSources = dassourceBrowser
1649 .getSelectedSources();
1650 doDasFeatureFetch(selectedSources, true, true);
1654 * get the features from selectedSources for all or the current selection
1656 * @param selectedSources
1657 * @param checkDbRefs
1658 * @param promptFetchDbRefs
1660 private void doDasFeatureFetch(List<jalviewSourceI> selectedSources,
1661 boolean checkDbRefs, boolean promptFetchDbRefs)
1663 SequenceI[] dataset, seqs;
1665 AlignmentViewport vp = af.getViewport();
1666 if (vp.getSelectionGroup() != null
1667 && vp.getSelectionGroup().getSize() > 0)
1669 iSize = vp.getSelectionGroup().getSize();
1670 dataset = new SequenceI[iSize];
1671 seqs = vp.getSelectionGroup().getSequencesInOrder(vp.getAlignment());
1675 iSize = vp.getAlignment().getHeight();
1676 seqs = vp.getAlignment().getSequencesArray();
1679 dataset = new SequenceI[iSize];
1680 for (int i = 0; i < iSize; i++)
1682 dataset[i] = seqs[i].getDatasetSequence();
1685 cancelDAS.setEnabled(true);
1686 dasFeatureFetcher = new jalview.ws.DasSequenceFeatureFetcher(dataset,
1687 this, selectedSources, checkDbRefs, promptFetchDbRefs);
1688 af.getViewport().setShowSequenceFeatures(true);
1689 af.showSeqFeatures.setSelected(true);
1693 * blocking call to initialise the das source browser
1695 public void initDasSources()
1697 dassourceBrowser.initDasSources();
1701 * examine the current list of das sources and return any matching the given
1702 * nicknames in sources
1705 * Vector of Strings to resolve to DAS source nicknames.
1706 * @return sources that are present in source list.
1708 public List<jalviewSourceI> resolveSourceNicknames(Vector<String> sources)
1710 return dassourceBrowser.sourceRegistry.resolveSourceNicknames(sources);
1714 * get currently selected das sources. ensure you have called initDasSources
1715 * before calling this.
1717 * @return vector of selected das source nicknames
1719 public Vector<jalviewSourceI> getSelectedSources()
1721 return dassourceBrowser.getSelectedSources();
1725 * properly initialise DAS fetcher and then initiate a new thread to fetch
1726 * features from the named sources (rather than any turned on by default)
1730 * if true then runs in same thread, otherwise passes to the Swing
1733 public void fetchDasFeatures(Vector<String> sources, boolean block)
1736 List<jalviewSourceI> resolved = dassourceBrowser.sourceRegistry
1737 .resolveSourceNicknames(sources);
1738 if (resolved.size() == 0)
1740 resolved = dassourceBrowser.getSelectedSources();
1742 if (resolved.size() > 0)
1744 final List<jalviewSourceI> dassources = resolved;
1745 fetchDAS.setEnabled(false);
1746 // cancelDAS.setEnabled(true); doDasFetch does this.
1747 Runnable fetcher = new Runnable()
1753 doDasFeatureFetch(dassources, true, false);
1763 SwingUtilities.invokeLater(fetcher);
1768 public void saveDAS_actionPerformed(ActionEvent e)
1771 .saveProperties(jalview.bin.Cache.applicationProperties);
1774 public void complete()
1776 fetchDAS.setEnabled(true);
1777 cancelDAS.setEnabled(false);
1778 dassourceBrowser.setGuiEnabled(true);
1782 public void cancelDAS_actionPerformed(ActionEvent e)
1784 if (dasFeatureFetcher != null)
1786 dasFeatureFetcher.cancel();
1791 public void noDasSourceActive()
1794 JvOptionPane.showInternalConfirmDialog(Desktop.desktop,
1795 MessageManager.getString("label.no_das_sources_selected_warn"),
1796 MessageManager.getString("label.no_das_sources_selected_title"),
1797 JvOptionPane.DEFAULT_OPTION, JvOptionPane.INFORMATION_MESSAGE);
1800 // ///////////////////////////////////////////////////////////////////////
1801 // http://java.sun.com/docs/books/tutorial/uiswing/components/table.html
1802 // ///////////////////////////////////////////////////////////////////////
1803 class FeatureTableModel extends AbstractTableModel
1805 private String[] columnNames = {
1806 MessageManager.getString("label.feature_type"),
1807 MessageManager.getString("action.colour"),
1808 MessageManager.getString("label.filter"),
1809 MessageManager.getString("label.show") };
1811 private Object[][] data;
1813 FeatureTableModel(Object[][] data)
1818 public Object[][] getData()
1823 public void setData(Object[][] data)
1829 public int getColumnCount()
1831 return columnNames.length;
1834 public Object[] getRow(int row)
1840 public int getRowCount()
1846 public String getColumnName(int col)
1848 return columnNames[col];
1852 public Object getValueAt(int row, int col)
1854 return data[row][col];
1858 * Answers the class of the object in column c of the first row of the table
1861 public Class<?> getColumnClass(int c)
1863 Object v = getValueAt(0, c);
1864 return v == null ? null : v.getClass();
1868 public boolean isCellEditable(int row, int col)
1870 return col == 0 ? false : true;
1874 public void setValueAt(Object value, int row, int col)
1876 data[row][col] = value;
1877 fireTableCellUpdated(row, col);
1878 updateFeatureRenderer(data);
1883 class ColorRenderer extends JLabel implements TableCellRenderer
1885 javax.swing.border.Border unselectedBorder = null;
1887 javax.swing.border.Border selectedBorder = null;
1889 final String baseTT = "Click to edit, right/apple click for menu.";
1891 public ColorRenderer()
1893 setOpaque(true); // MUST do this for background to show up.
1894 setHorizontalTextPosition(SwingConstants.CENTER);
1895 setVerticalTextPosition(SwingConstants.CENTER);
1899 public Component getTableCellRendererComponent(JTable tbl, Object color,
1900 boolean isSelected, boolean hasFocus, int row, int column)
1902 FeatureColourI cellColour = (FeatureColourI) color;
1904 setToolTipText(baseTT);
1905 setBackground(tbl.getBackground());
1906 if (!cellColour.isSimpleColour())
1908 Rectangle cr = tbl.getCellRect(row, column, false);
1909 FeatureSettings.renderGraduatedColor(this, cellColour,
1910 (int) cr.getWidth(), (int) cr.getHeight());
1916 setBackground(cellColour.getColour());
1920 if (selectedBorder == null)
1922 selectedBorder = BorderFactory.createMatteBorder(2, 5, 2, 5,
1923 tbl.getSelectionBackground());
1925 setBorder(selectedBorder);
1929 if (unselectedBorder == null)
1931 unselectedBorder = BorderFactory.createMatteBorder(2, 5, 2, 5,
1932 tbl.getBackground());
1934 setBorder(unselectedBorder);
1941 class FilterRenderer extends JLabel implements TableCellRenderer
1943 javax.swing.border.Border unselectedBorder = null;
1945 javax.swing.border.Border selectedBorder = null;
1947 public FilterRenderer()
1949 setOpaque(true); // MUST do this for background to show up.
1950 setHorizontalTextPosition(SwingConstants.CENTER);
1951 setVerticalTextPosition(SwingConstants.CENTER);
1955 public Component getTableCellRendererComponent(JTable tbl,
1956 Object filter, boolean isSelected, boolean hasFocus, int row,
1959 FeatureMatcherSetI theFilter = (FeatureMatcherSetI) filter;
1961 String asText = theFilter.toString();
1962 setBackground(tbl.getBackground());
1963 this.setText(asText);
1968 if (selectedBorder == null)
1970 selectedBorder = BorderFactory.createMatteBorder(2, 5, 2, 5,
1971 tbl.getSelectionBackground());
1973 setBorder(selectedBorder);
1977 if (unselectedBorder == null)
1979 unselectedBorder = BorderFactory.createMatteBorder(2, 5, 2, 5,
1980 tbl.getBackground());
1982 setBorder(unselectedBorder);
1990 * update comp using rendering settings from gcol
1995 public static void renderGraduatedColor(JLabel comp, FeatureColourI gcol)
1997 int w = comp.getWidth(), h = comp.getHeight();
2000 w = (int) comp.getPreferredSize().getWidth();
2001 h = (int) comp.getPreferredSize().getHeight();
2008 renderGraduatedColor(comp, gcol, w, h);
2011 public static void renderGraduatedColor(JLabel comp, FeatureColourI gcol,
2014 boolean thr = false;
2015 StringBuilder tt = new StringBuilder();
2016 StringBuilder tx = new StringBuilder();
2018 if (gcol.isColourByAttribute())
2020 tx.append(String.join(":", gcol.getAttributeName()));
2022 else if (!gcol.isColourByLabel())
2024 tx.append(MessageManager.getString("label.score"));
2027 if (gcol.isAboveThreshold())
2031 tt.append("Thresholded (Above ").append(gcol.getThreshold())
2034 if (gcol.isBelowThreshold())
2038 tt.append("Thresholded (Below ").append(gcol.getThreshold())
2041 if (gcol.isColourByLabel())
2043 tt.append("Coloured by label text. ").append(tt);
2048 if (!gcol.isColourByAttribute())
2056 Color newColor = gcol.getMaxColour();
2057 comp.setBackground(newColor);
2058 // System.err.println("Width is " + w / 2);
2059 Icon ficon = new FeatureIcon(gcol, comp.getBackground(), w, h, thr);
2060 comp.setIcon(ficon);
2061 // tt+="RGB value: Max (" + newColor.getRed() + ", "
2062 // + newColor.getGreen() + ", " + newColor.getBlue()
2063 // + ")\nMin (" + minCol.getRed() + ", " + minCol.getGreen()
2064 // + ", " + minCol.getBlue() + ")");
2066 comp.setHorizontalAlignment(SwingConstants.CENTER);
2067 comp.setText(tx.toString());
2068 if (tt.length() > 0)
2070 if (comp.getToolTipText() == null)
2072 comp.setToolTipText(tt.toString());
2076 comp.setToolTipText(
2077 tt.append(" ").append(comp.getToolTipText()).toString());
2082 class ColorEditor extends AbstractCellEditor
2083 implements TableCellEditor, ActionListener
2087 FeatureColourI currentColor;
2089 FeatureTypeSettings chooser;
2095 JColorChooser colorChooser;
2099 protected static final String EDIT = "edit";
2101 int rowSelected = 0;
2103 public ColorEditor(FeatureSettings me)
2106 // Set up the editor (from the table's point of view),
2107 // which is a button.
2108 // This button brings up the color chooser dialog,
2109 // which is the editor from the user's point of view.
2110 button = new JButton();
2111 button.setActionCommand(EDIT);
2112 button.addActionListener(this);
2113 button.setBorderPainted(false);
2114 // Set up the dialog that the button brings up.
2115 colorChooser = new JColorChooser();
2116 dialog = JColorChooser.createDialog(button,
2117 MessageManager.getString("label.select_colour"), true, // modal
2118 colorChooser, this, // OK button handler
2119 null); // no CANCEL button handler
2123 * Handles events from the editor button and from the dialog's OK button.
2126 public void actionPerformed(ActionEvent e)
2128 // todo test e.getSource() instead here
2129 if (EDIT.equals(e.getActionCommand()))
2131 // The user has clicked the cell, so
2132 // bring up the dialog.
2133 if (currentColor.isSimpleColour())
2135 // bring up simple color chooser
2136 button.setBackground(currentColor.getColour());
2137 colorChooser.setColor(currentColor.getColour());
2138 dialog.setVisible(true);
2142 // bring up graduated chooser.
2143 chooser = new FeatureTypeSettings(me.fr, type);
2144 chooser.setRequestFocusEnabled(true);
2145 chooser.requestFocus();
2146 chooser.addActionListener(this);
2147 chooser.showTab(true);
2149 // Make the renderer reappear.
2150 fireEditingStopped();
2155 if (currentColor.isSimpleColour())
2158 * read off colour picked in colour chooser after OK pressed
2160 currentColor = new FeatureColour(colorChooser.getColor());
2161 me.table.setValueAt(currentColor, rowSelected, COLOUR_COLUMN);
2166 * after OK in variable colour dialog, any changes to colour
2167 * (or filters!) are already set in FeatureRenderer, so just
2168 * update table data without triggering updateFeatureRenderer
2170 currentColor = fr.getFeatureColours().get(type);
2171 FeatureMatcherSetI currentFilter = me.fr.getFeatureFilter(type);
2172 if (currentFilter == null)
2174 currentFilter = new FeatureMatcherSet();
2176 Object[] data = ((FeatureTableModel) table.getModel())
2177 .getData()[rowSelected];
2178 data[COLOUR_COLUMN] = currentColor;
2179 data[FILTER_COLUMN] = currentFilter;
2181 fireEditingStopped();
2182 me.table.validate();
2186 // Implement the one CellEditor method that AbstractCellEditor doesn't.
2188 public Object getCellEditorValue()
2190 return currentColor;
2193 // Implement the one method defined by TableCellEditor.
2195 public Component getTableCellEditorComponent(JTable theTable, Object value,
2196 boolean isSelected, int row, int column)
2198 currentColor = (FeatureColourI) value;
2199 this.rowSelected = row;
2200 type = me.table.getValueAt(row, TYPE_COLUMN).toString();
2201 button.setOpaque(true);
2202 button.setBackground(me.getBackground());
2203 if (!currentColor.isSimpleColour())
2205 JLabel btn = new JLabel();
2206 btn.setSize(button.getSize());
2207 FeatureSettings.renderGraduatedColor(btn, currentColor);
2208 button.setBackground(btn.getBackground());
2209 button.setIcon(btn.getIcon());
2210 button.setText(btn.getText());
2215 button.setIcon(null);
2216 button.setBackground(currentColor.getColour());
2223 * The cell editor for the Filter column. It displays the text of any filters
2224 * for the feature type in that row (in full as a tooltip, possible abbreviated
2225 * as display text). On click in the cell, opens the Feature Display Settings
2226 * dialog at the Filters tab.
2228 class FilterEditor extends AbstractCellEditor
2229 implements TableCellEditor, ActionListener
2233 FeatureMatcherSetI currentFilter;
2241 protected static final String EDIT = "edit";
2243 int rowSelected = 0;
2245 public FilterEditor(FeatureSettings me)
2248 button = new JButton();
2249 button.setActionCommand(EDIT);
2250 button.addActionListener(this);
2251 button.setBorderPainted(false);
2255 * Handles events from the editor button
2258 public void actionPerformed(ActionEvent e)
2260 if (button == e.getSource())
2262 FeatureTypeSettings chooser = new FeatureTypeSettings(me.fr, type);
2263 chooser.addActionListener(this);
2264 chooser.setRequestFocusEnabled(true);
2265 chooser.requestFocus();
2266 if (lastLocation != null)
2268 // todo open at its last position on screen
2269 chooser.setBounds(lastLocation.x, lastLocation.y,
2270 chooser.getWidth(), chooser.getHeight());
2273 chooser.showTab(false);
2274 fireEditingStopped();
2276 else if (e.getSource() instanceof Component)
2280 * after OK in variable colour dialog, any changes to filter
2281 * (or colours!) are already set in FeatureRenderer, so just
2282 * update table data without triggering updateFeatureRenderer
2284 FeatureColourI currentColor = fr.getFeatureColours().get(type);
2285 currentFilter = me.fr.getFeatureFilter(type);
2286 if (currentFilter == null)
2288 currentFilter = new FeatureMatcherSet();
2290 Object[] data = ((FeatureTableModel) table.getModel())
2291 .getData()[rowSelected];
2292 data[COLOUR_COLUMN] = currentColor;
2293 data[FILTER_COLUMN] = currentFilter;
2294 fireEditingStopped();
2295 me.table.validate();
2300 public Object getCellEditorValue()
2302 return currentFilter;
2306 public Component getTableCellEditorComponent(JTable theTable, Object value,
2307 boolean isSelected, int row, int column)
2309 currentFilter = (FeatureMatcherSetI) value;
2310 this.rowSelected = row;
2311 type = me.table.getValueAt(row, TYPE_COLUMN).toString();
2312 button.setOpaque(true);
2313 button.setBackground(me.getBackground());
2314 button.setText(currentFilter.toString());
2315 button.setToolTipText(currentFilter.toString());
2316 button.setIcon(null);
2322 class FeatureIcon implements Icon
2324 FeatureColourI gcol;
2328 boolean midspace = false;
2330 int width = 50, height = 20;
2332 int s1, e1; // start and end of midpoint band for thresholded symbol
2334 Color mpcolour = Color.white;
2336 FeatureIcon(FeatureColourI gfc, Color bg, int w, int h, boolean mspace)
2356 public int getIconWidth()
2362 public int getIconHeight()
2368 public void paintIcon(Component c, Graphics g, int x, int y)
2371 if (gcol.isColourByLabel())
2374 g.fillRect(0, 0, width, height);
2375 // need an icon here.
2376 g.setColor(gcol.getMaxColour());
2378 g.setFont(new Font("Verdana", Font.PLAIN, 9));
2380 // g.setFont(g.getFont().deriveFont(
2381 // AffineTransform.getScaleInstance(
2382 // width/g.getFontMetrics().stringWidth("Label"),
2383 // height/g.getFontMetrics().getHeight())));
2385 g.drawString(MessageManager.getString("label.label"), 0, 0);
2390 Color minCol = gcol.getMinColour();
2392 g.fillRect(0, 0, s1, height);
2395 g.setColor(Color.white);
2396 g.fillRect(s1, 0, e1 - s1, height);
2398 g.setColor(gcol.getMaxColour());
2399 g.fillRect(0, e1, width - e1, height);