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.datamodel.AlignmentI;
26 import jalview.datamodel.SequenceI;
27 import jalview.datamodel.features.FeatureMatcherI;
28 import jalview.datamodel.features.FeatureMatcherSet;
29 import jalview.datamodel.features.FeatureMatcherSetI;
30 import jalview.gui.Help.HelpId;
31 import jalview.io.JalviewFileChooser;
32 import jalview.io.JalviewFileView;
33 import jalview.schemabinding.version2.Filter;
34 import jalview.schemabinding.version2.JalviewUserColours;
35 import jalview.schemabinding.version2.MatcherSet;
36 import jalview.schemes.FeatureColour;
37 import jalview.util.MessageManager;
38 import jalview.util.Platform;
39 import jalview.viewmodel.seqfeatures.FeatureRendererModel.FeatureSettingsBean;
41 import java.awt.BorderLayout;
42 import java.awt.Color;
43 import java.awt.Component;
44 import java.awt.Dimension;
46 import java.awt.Graphics;
47 import java.awt.GridLayout;
48 import java.awt.Point;
49 import java.awt.Rectangle;
50 import java.awt.event.ActionEvent;
51 import java.awt.event.ActionListener;
52 import java.awt.event.ItemEvent;
53 import java.awt.event.ItemListener;
54 import java.awt.event.MouseAdapter;
55 import java.awt.event.MouseEvent;
56 import java.awt.event.MouseMotionAdapter;
57 import java.beans.PropertyChangeEvent;
58 import java.beans.PropertyChangeListener;
60 import java.io.FileInputStream;
61 import java.io.FileOutputStream;
62 import java.io.InputStreamReader;
63 import java.io.OutputStreamWriter;
64 import java.io.PrintWriter;
65 import java.util.Arrays;
66 import java.util.Comparator;
67 import java.util.HashMap;
68 import java.util.HashSet;
69 import java.util.Hashtable;
70 import java.util.Iterator;
71 import java.util.List;
75 import javax.help.HelpSetException;
76 import javax.swing.AbstractCellEditor;
77 import javax.swing.BorderFactory;
78 import javax.swing.Icon;
79 import javax.swing.JButton;
80 import javax.swing.JCheckBox;
81 import javax.swing.JCheckBoxMenuItem;
82 import javax.swing.JColorChooser;
83 import javax.swing.JDialog;
84 import javax.swing.JInternalFrame;
85 import javax.swing.JLabel;
86 import javax.swing.JLayeredPane;
87 import javax.swing.JMenuItem;
88 import javax.swing.JPanel;
89 import javax.swing.JPopupMenu;
90 import javax.swing.JScrollPane;
91 import javax.swing.JSlider;
92 import javax.swing.JTable;
93 import javax.swing.ListSelectionModel;
94 import javax.swing.SwingConstants;
95 import javax.swing.event.ChangeEvent;
96 import javax.swing.event.ChangeListener;
97 import javax.swing.table.AbstractTableModel;
98 import javax.swing.table.JTableHeader;
99 import javax.swing.table.TableCellEditor;
100 import javax.swing.table.TableCellRenderer;
101 import javax.swing.table.TableColumn;
103 public class FeatureSettings extends JPanel
104 implements FeatureSettingsControllerI
106 private static final String SEQUENCE_FEATURE_COLOURS = MessageManager
107 .getString("label.sequence_feature_colours");
110 * column indices of fields in Feature Settings table
112 static final int TYPE_COLUMN = 0;
114 static final int COLOUR_COLUMN = 1;
116 static final int FILTER_COLUMN = 2;
118 static final int SHOW_COLUMN = 3;
120 private static final int COLUMN_COUNT = 4;
122 private static final int MIN_WIDTH = 400;
124 private static final int MIN_HEIGHT = 400;
126 final FeatureRenderer fr;
128 public final AlignFrame af;
131 * 'original' fields hold settings to restore on Cancel
133 Object[][] originalData;
135 private float originalTransparency;
137 private Map<String, FeatureMatcherSetI> originalFilters;
139 final JInternalFrame frame;
141 JScrollPane scrollPane = new JScrollPane();
147 JSlider transparency = new JSlider();
150 * when true, constructor is still executing - so ignore UI events
152 protected volatile boolean inConstruction = true;
154 int selectedRow = -1;
156 JButton fetchDAS = new JButton();
158 JButton saveDAS = new JButton();
160 JButton cancelDAS = new JButton();
162 boolean resettingTable = false;
165 * true when Feature Settings are updating from feature renderer
167 private boolean handlingUpdate = false;
170 * holds {featureCount, totalExtent} for each feature type
172 Map<String, float[]> typeWidth = null;
179 public FeatureSettings(AlignFrame alignFrame)
181 this.af = alignFrame;
182 fr = af.getFeatureRenderer();
184 // save transparency for restore on Cancel
185 originalTransparency = fr.getTransparency();
186 int originalTransparencyAsPercent = (int) (originalTransparency * 100);
187 transparency.setMaximum(100 - originalTransparencyAsPercent);
189 originalFilters = new HashMap<>(fr.getFeatureFilters()); // shallow copy
194 } catch (Exception ex)
196 ex.printStackTrace();
202 public String getToolTipText(MouseEvent e)
205 int column = table.columnAtPoint(e.getPoint());
209 tip = JvSwingUtils.wrapTooltip(true, MessageManager
210 .getString("label.feature_settings_click_drag"));
213 int row = table.rowAtPoint(e.getPoint());
214 FeatureMatcherSet o = (FeatureMatcherSet) table.getValueAt(row,
217 ? MessageManager.getString("label.filters_tooltip")
226 JTableHeader tableHeader = table.getTableHeader();
227 tableHeader.setFont(new Font("Verdana", Font.PLAIN, 12));
228 tableHeader.setReorderingAllowed(false);
229 table.setFont(new Font("Verdana", Font.PLAIN, 12));
231 // table.setDefaultRenderer(Color.class, new ColorRenderer());
232 // table.setDefaultEditor(Color.class, new ColorEditor(this));
234 table.setDefaultEditor(FeatureColour.class, new ColorEditor(this));
235 table.setDefaultRenderer(FeatureColour.class, new ColorRenderer());
237 table.setDefaultEditor(FeatureMatcherSet.class, new FilterEditor(this));
238 table.setDefaultRenderer(FeatureMatcherSet.class, new FilterRenderer());
240 TableColumn colourColumn = new TableColumn(COLOUR_COLUMN, 75,
241 new ColorRenderer(), new ColorEditor(this));
242 table.addColumn(colourColumn);
244 TableColumn filterColumn = new TableColumn(FILTER_COLUMN, 75,
245 new FilterRenderer(), new FilterEditor(this));
246 table.addColumn(filterColumn);
248 table.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
250 table.addMouseListener(new MouseAdapter()
253 public void mousePressed(MouseEvent evt)
255 selectedRow = table.rowAtPoint(evt.getPoint());
256 String type = (String) table.getValueAt(selectedRow, TYPE_COLUMN);
257 if (evt.isPopupTrigger())
259 Object colour = table.getValueAt(selectedRow, COLOUR_COLUMN);
260 popupSort(selectedRow, type, colour, fr.getMinMax(), evt.getX(),
263 else if (evt.getClickCount() == 2)
265 boolean invertSelection = evt.isAltDown();
266 boolean toggleSelection = Platform.isControlDown(evt);
267 boolean extendSelection = evt.isShiftDown();
268 fr.ap.alignFrame.avc.markColumnsContainingFeatures(
269 invertSelection, extendSelection, toggleSelection, type);
273 // isPopupTrigger fires on mouseReleased on Windows
275 public void mouseReleased(MouseEvent evt)
277 selectedRow = table.rowAtPoint(evt.getPoint());
278 if (evt.isPopupTrigger())
280 String type = (String) table.getValueAt(selectedRow, TYPE_COLUMN);
281 Object colour = table.getValueAt(selectedRow, COLOUR_COLUMN);
282 popupSort(selectedRow, type, colour, fr.getMinMax(), evt.getX(),
288 table.addMouseMotionListener(new MouseMotionAdapter()
291 public void mouseDragged(MouseEvent evt)
293 int newRow = table.rowAtPoint(evt.getPoint());
294 if (newRow != selectedRow && selectedRow != -1 && newRow != -1)
297 * reposition 'selectedRow' to 'newRow' (the dragged to location)
298 * this could be more than one row away for a very fast drag action
299 * so just swap it with adjacent rows until we get it there
301 Object[][] data = ((FeatureTableModel) table.getModel())
303 int direction = newRow < selectedRow ? -1 : 1;
304 for (int i = selectedRow; i != newRow; i += direction)
306 Object[] temp = data[i];
307 data[i] = data[i + direction];
308 data[i + direction] = temp;
310 updateFeatureRenderer(data);
312 selectedRow = newRow;
316 // table.setToolTipText(JvSwingUtils.wrapTooltip(true,
317 // MessageManager.getString("label.feature_settings_click_drag")));
318 scrollPane.setViewportView(table);
320 if (af.getViewport().isShowSequenceFeatures() || !fr.hasRenderOrder())
322 fr.findAllFeatures(true); // display everything!
325 discoverAllFeatureData();
326 final PropertyChangeListener change;
327 final FeatureSettings fs = this;
328 fr.addPropertyChangeListener(change = new PropertyChangeListener()
331 public void propertyChange(PropertyChangeEvent evt)
333 if (!fs.resettingTable && !fs.handlingUpdate)
335 fs.handlingUpdate = true;
337 // new groups may be added with new sequence feature types only
338 fs.handlingUpdate = false;
344 frame = new JInternalFrame();
345 frame.setContentPane(this);
346 if (Platform.isAMac())
348 Desktop.addInternalFrame(frame,
349 MessageManager.getString("label.sequence_feature_settings"),
354 Desktop.addInternalFrame(frame,
355 MessageManager.getString("label.sequence_feature_settings"),
358 frame.setMinimumSize(new Dimension(MIN_WIDTH, MIN_HEIGHT));
360 frame.addInternalFrameListener(
361 new javax.swing.event.InternalFrameAdapter()
364 public void internalFrameClosed(
365 javax.swing.event.InternalFrameEvent evt)
367 fr.removePropertyChangeListener(change);
370 frame.setLayer(JLayeredPane.PALETTE_LAYER);
371 inConstruction = false;
374 protected void popupSort(final int rowSelected, final String type,
375 final Object typeCol, final Map<String, float[][]> minmax, int x,
378 final FeatureColourI featureColour = (FeatureColourI) typeCol;
380 JPopupMenu men = new JPopupMenu(MessageManager
381 .formatMessage("label.settings_for_param", new String[]
383 JMenuItem scr = new JMenuItem(
384 MessageManager.getString("label.sort_by_score"));
386 final FeatureSettings me = this;
387 scr.addActionListener(new ActionListener()
391 public void actionPerformed(ActionEvent e)
394 .sortAlignmentByFeatureScore(Arrays.asList(new String[]
399 JMenuItem dens = new JMenuItem(
400 MessageManager.getString("label.sort_by_density"));
401 dens.addActionListener(new ActionListener()
405 public void actionPerformed(ActionEvent e)
408 .sortAlignmentByFeatureDensity(Arrays.asList(new String[]
416 * variable colour options include colour by label, by score,
417 * by selected attribute text, or attribute value
419 final JCheckBoxMenuItem mxcol = new JCheckBoxMenuItem(
420 MessageManager.getString("label.variable_colour"));
421 mxcol.setSelected(!featureColour.isSimpleColour());
423 mxcol.addActionListener(new ActionListener()
425 JColorChooser colorChooser;
428 public void actionPerformed(ActionEvent e)
430 if (e.getSource() == mxcol)
432 if (featureColour.isSimpleColour())
434 FeatureTypeSettings fc = new FeatureTypeSettings(me.fr, type);
435 fc.addActionListener(this);
439 // bring up simple color chooser
440 colorChooser = new JColorChooser();
441 String title = MessageManager
442 .getString("label.select_colour");
443 JDialog dialog = JColorChooser.createDialog(me,
444 title, true, // modal
445 colorChooser, this, // OK button handler
446 null); // no CANCEL button handler
447 colorChooser.setColor(featureColour.getMaxColour());
448 dialog.setVisible(true);
453 if (e.getSource() instanceof FeatureTypeSettings)
456 * update after OK in feature colour dialog; the updated
457 * colour will have already been set in the FeatureRenderer
459 FeatureColourI fci = fr.getFeatureColours().get(type);
460 table.setValueAt(fci, rowSelected, 1);
465 // probably the color chooser!
466 table.setValueAt(new FeatureColour(colorChooser.getColor()),
469 me.updateFeatureRenderer(
470 ((FeatureTableModel) table.getModel()).getData(),
478 JMenuItem selCols = new JMenuItem(
479 MessageManager.getString("label.select_columns_containing"));
480 selCols.addActionListener(new ActionListener()
483 public void actionPerformed(ActionEvent arg0)
485 fr.ap.alignFrame.avc.markColumnsContainingFeatures(false, false,
489 JMenuItem clearCols = new JMenuItem(MessageManager
490 .getString("label.select_columns_not_containing"));
491 clearCols.addActionListener(new ActionListener()
494 public void actionPerformed(ActionEvent arg0)
496 fr.ap.alignFrame.avc.markColumnsContainingFeatures(true, false,
500 JMenuItem hideCols = new JMenuItem(
501 MessageManager.getString("label.hide_columns_containing"));
502 hideCols.addActionListener(new ActionListener()
505 public void actionPerformed(ActionEvent arg0)
507 fr.ap.alignFrame.hideFeatureColumns(type, true);
510 JMenuItem hideOtherCols = new JMenuItem(
511 MessageManager.getString("label.hide_columns_not_containing"));
512 hideOtherCols.addActionListener(new ActionListener()
515 public void actionPerformed(ActionEvent arg0)
517 fr.ap.alignFrame.hideFeatureColumns(type, false);
523 men.add(hideOtherCols);
524 men.show(table, x, y);
528 synchronized public void discoverAllFeatureData()
530 Set<String> allGroups = new HashSet<>();
531 AlignmentI alignment = af.getViewport().getAlignment();
533 for (int i = 0; i < alignment.getHeight(); i++)
535 SequenceI seq = alignment.getSequenceAt(i);
536 for (String group : seq.getFeatures().getFeatureGroups(true))
538 if (group != null && !allGroups.contains(group))
540 allGroups.add(group);
541 checkGroupState(group);
552 * Synchronise gui group list and check visibility of group
555 * @return true if group is visible
557 private boolean checkGroupState(String group)
559 boolean visible = fr.checkGroupVisibility(group, true);
561 for (int g = 0; g < groupPanel.getComponentCount(); g++)
563 if (((JCheckBox) groupPanel.getComponent(g)).getText().equals(group))
565 ((JCheckBox) groupPanel.getComponent(g)).setSelected(visible);
570 final String grp = group;
571 final JCheckBox check = new JCheckBox(group, visible);
572 check.setFont(new Font("Serif", Font.BOLD, 12));
573 check.setToolTipText(group);
574 check.addItemListener(new ItemListener()
577 public void itemStateChanged(ItemEvent evt)
579 fr.setGroupVisibility(check.getText(), check.isSelected());
580 resetTable(new String[] { grp });
581 af.alignPanel.paintAlignment(true, true);
584 groupPanel.add(check);
588 synchronized void resetTable(String[] groupChanged)
594 resettingTable = true;
595 typeWidth = new Hashtable<>();
596 // TODO: change avWidth calculation to 'per-sequence' average and use long
599 Set<String> displayableTypes = new HashSet<>();
600 Set<String> foundGroups = new HashSet<>();
603 * determine which feature types may be visible depending on
604 * which groups are selected, and recompute average width data
606 for (int i = 0; i < af.getViewport().getAlignment().getHeight(); i++)
609 SequenceI seq = af.getViewport().getAlignment().getSequenceAt(i);
612 * get the sequence's groups for positional features
613 * and keep track of which groups are visible
615 Set<String> groups = seq.getFeatures().getFeatureGroups(true);
616 Set<String> visibleGroups = new HashSet<>();
617 for (String group : groups)
619 if (group == null || checkGroupState(group))
621 visibleGroups.add(group);
624 foundGroups.addAll(groups);
627 * get distinct feature types for visible groups
628 * record distinct visible types, and their count and total length
630 Set<String> types = seq.getFeatures().getFeatureTypesForGroups(true,
631 visibleGroups.toArray(new String[visibleGroups.size()]));
632 for (String type : types)
634 displayableTypes.add(type);
635 float[] avWidth = typeWidth.get(type);
638 avWidth = new float[2];
639 typeWidth.put(type, avWidth);
641 // todo this could include features with a non-visible group
642 // - do we greatly care?
643 // todo should we include non-displayable features here, and only
644 // update when features are added?
645 avWidth[0] += seq.getFeatures().getFeatureCount(true, type);
646 avWidth[1] += seq.getFeatures().getTotalFeatureLength(type);
650 Object[][] data = new Object[displayableTypes.size()][COLUMN_COUNT];
653 if (fr.hasRenderOrder())
657 fr.findAllFeatures(groupChanged != null); // prod to update
658 // colourschemes. but don't
660 // First add the checks in the previous render order,
661 // in case the window has been closed and reopened
663 List<String> frl = fr.getRenderOrder();
664 for (int ro = frl.size() - 1; ro > -1; ro--)
666 String type = frl.get(ro);
668 if (!displayableTypes.contains(type))
673 data[dataIndex][TYPE_COLUMN] = type;
674 data[dataIndex][COLOUR_COLUMN] = fr.getFeatureStyle(type);
675 FeatureMatcherSetI featureFilter = fr.getFeatureFilter(type);
676 data[dataIndex][FILTER_COLUMN] = featureFilter == null
677 ? new FeatureMatcherSet()
679 data[dataIndex][SHOW_COLUMN] = new Boolean(
680 af.getViewport().getFeaturesDisplayed().isVisible(type));
682 displayableTypes.remove(type);
687 * process any extra features belonging only to
688 * a group which was just selected
690 while (!displayableTypes.isEmpty())
692 String type = displayableTypes.iterator().next();
693 data[dataIndex][TYPE_COLUMN] = type;
695 data[dataIndex][COLOUR_COLUMN] = fr.getFeatureStyle(type);
696 if (data[dataIndex][COLOUR_COLUMN] == null)
698 // "Colour has been updated in another view!!"
699 fr.clearRenderOrder();
702 FeatureMatcherSetI featureFilter = fr.getFeatureFilter(type);
703 data[dataIndex][FILTER_COLUMN] = featureFilter == null
704 ? new FeatureMatcherSet()
706 data[dataIndex][SHOW_COLUMN] = new Boolean(true);
708 displayableTypes.remove(type);
711 if (originalData == null)
713 originalData = new Object[data.length][COLUMN_COUNT];
714 for (int i = 0; i < data.length; i++)
716 System.arraycopy(data[i], 0, originalData[i], 0, COLUMN_COUNT);
721 updateOriginalData(data);
724 table.setModel(new FeatureTableModel(data));
725 table.getColumnModel().getColumn(0).setPreferredWidth(200);
727 groupPanel.setLayout(
728 new GridLayout(fr.getFeatureGroupsSize() / 4 + 1, 4));
729 pruneGroups(foundGroups);
730 groupPanel.validate();
732 updateFeatureRenderer(data, groupChanged != null);
733 resettingTable = false;
737 * Updates 'originalData' (used for restore on Cancel) if we detect that changes
738 * have been made outwith this dialog
740 * <li>a new feature type added (and made visible)</li>
741 * <li>a feature colour changed (in the Amend Features dialog)</li>
746 protected void updateOriginalData(Object[][] foundData)
748 // todo LinkedHashMap instead of Object[][] would be nice
750 Object[][] currentData = ((FeatureTableModel) table.getModel())
752 for (Object[] row : foundData)
754 String type = (String) row[TYPE_COLUMN];
755 boolean found = false;
756 for (Object[] current : currentData)
758 if (type.equals(current[TYPE_COLUMN]))
762 * currently dependent on object equality here;
763 * really need an equals method on FeatureColour
765 if (!row[COLOUR_COLUMN].equals(current[COLOUR_COLUMN]))
768 * feature colour has changed externally - update originalData
770 for (Object[] original : originalData)
772 if (type.equals(original[TYPE_COLUMN]))
774 original[COLOUR_COLUMN] = row[COLOUR_COLUMN];
785 * new feature detected - add to original data (on top)
787 Object[][] newData = new Object[originalData.length
789 for (int i = 0; i < originalData.length; i++)
791 System.arraycopy(originalData[i], 0, newData[i + 1], 0,
795 originalData = newData;
801 * Remove from the groups panel any checkboxes for groups that are not in the
802 * foundGroups set. This enables removing a group from the display when the last
803 * feature in that group is deleted.
807 protected void pruneGroups(Set<String> foundGroups)
809 for (int g = 0; g < groupPanel.getComponentCount(); g++)
811 JCheckBox checkbox = (JCheckBox) groupPanel.getComponent(g);
812 if (!foundGroups.contains(checkbox.getText()))
814 groupPanel.remove(checkbox);
820 * reorder data based on the featureRenderers global priority list.
824 private void ensureOrder(Object[][] data)
826 boolean sort = false;
827 float[] order = new float[data.length];
828 for (int i = 0; i < order.length; i++)
830 order[i] = fr.getOrder(data[i][0].toString());
833 order[i] = fr.setOrder(data[i][0].toString(), i / order.length);
837 sort = sort || order[i - 1] > order[i];
842 jalview.util.QuickSort.sort(order, data);
847 * Offers a file chooser dialog, and then loads the feature colours and
848 * filters from file in XML format and unmarshals to Jalview feature settings
852 JalviewFileChooser chooser = new JalviewFileChooser("fc",
853 SEQUENCE_FEATURE_COLOURS);
854 chooser.setFileView(new JalviewFileView());
855 chooser.setDialogTitle(
856 MessageManager.getString("label.load_feature_colours"));
857 chooser.setToolTipText(MessageManager.getString("action.load"));
859 int value = chooser.showOpenDialog(this);
861 if (value == JalviewFileChooser.APPROVE_OPTION)
863 File file = chooser.getSelectedFile();
869 * Loads feature colours and filters from XML stored in the given file
877 InputStreamReader in = new InputStreamReader(
878 new FileInputStream(file), "UTF-8");
880 JalviewUserColours jucs = JalviewUserColours.unmarshal(in);
883 * load feature colours
885 for (int i = jucs.getColourCount() - 1; i >= 0; i--)
887 jalview.schemabinding.version2.Colour newcol = jucs.getColour(i);
888 FeatureColourI colour = Jalview2XML.unmarshalColour(newcol);
889 fr.setColour(newcol.getName(), colour);
890 fr.setOrder(newcol.getName(), i / (float) jucs.getColourCount());
894 * load feature filters; loaded filters will replace any that are
895 * currently defined, other defined filters are left unchanged
897 for (int i = 0; i < jucs.getFilterCount(); i++)
899 jalview.schemabinding.version2.Filter filterModel = jucs
901 String featureType = filterModel.getFeatureType();
902 FeatureMatcherSetI filter = Jalview2XML.unmarshalFilter(featureType,
903 filterModel.getMatcherSet());
904 if (!filter.isEmpty())
906 fr.setFeatureFilter(featureType, filter);
911 * update feature settings table
916 Object[][] data = ((FeatureTableModel) table.getModel())
919 updateFeatureRenderer(data, false);
922 } catch (Exception ex)
924 System.out.println("Error loading User Colour File\n" + ex);
929 * Offers a file chooser dialog, and then saves the current feature colours
930 * and any filters to the selected file in XML format
934 JalviewFileChooser chooser = new JalviewFileChooser("fc",
935 SEQUENCE_FEATURE_COLOURS);
936 chooser.setFileView(new JalviewFileView());
937 chooser.setDialogTitle(
938 MessageManager.getString("label.save_feature_colours"));
939 chooser.setToolTipText(MessageManager.getString("action.save"));
941 int value = chooser.showSaveDialog(this);
943 if (value == JalviewFileChooser.APPROVE_OPTION)
945 save(chooser.getSelectedFile());
950 * Saves feature colours and filters to the given file
956 JalviewUserColours ucs = new JalviewUserColours();
957 ucs.setSchemeName("Sequence Features");
960 PrintWriter out = new PrintWriter(new OutputStreamWriter(
961 new FileOutputStream(file), "UTF-8"));
964 * sort feature types by colour order, from 0 (highest)
967 Set<String> fr_colours = fr.getAllFeatureColours();
968 String[] sortedTypes = fr_colours
969 .toArray(new String[fr_colours.size()]);
970 Arrays.sort(sortedTypes, new Comparator<String>()
973 public int compare(String type1, String type2)
975 return Float.compare(fr.getOrder(type1), fr.getOrder(type2));
980 * save feature colours
982 for (String featureType : sortedTypes)
984 FeatureColourI fcol = fr.getFeatureStyle(featureType);
985 jalview.schemabinding.version2.Colour col = Jalview2XML.marshalColour(
991 * save any feature filters
993 for (String featureType : sortedTypes)
995 FeatureMatcherSetI filter = fr.getFeatureFilter(featureType);
996 if (filter != null && !filter.isEmpty())
998 Iterator<FeatureMatcherI> iterator = filter.getMatchers().iterator();
999 FeatureMatcherI firstMatcher = iterator.next();
1000 MatcherSet ms = Jalview2XML.marshalFilter(firstMatcher, iterator,
1002 Filter filterModel = new Filter();
1003 filterModel.setFeatureType(featureType);
1004 filterModel.setMatcherSet(ms);
1005 ucs.addFilter(filterModel);
1011 } catch (Exception ex)
1013 ex.printStackTrace();
1017 public void invertSelection()
1019 Object[][] data = ((FeatureTableModel) table.getModel()).getData();
1020 for (int i = 0; i < data.length; i++)
1022 data[i][SHOW_COLUMN] = !(Boolean) data[i][SHOW_COLUMN];
1024 updateFeatureRenderer(data, true);
1028 public void orderByAvWidth()
1030 if (table == null || table.getModel() == null)
1034 Object[][] data = ((FeatureTableModel) table.getModel()).getData();
1035 float[] width = new float[data.length];
1039 for (int i = 0; i < data.length; i++)
1041 awidth = typeWidth.get(data[i][TYPE_COLUMN]);
1044 width[i] = awidth[1] / awidth[0];// *awidth[0]*awidth[2]; - better
1045 // weight - but have to make per
1046 // sequence, too (awidth[2])
1047 // if (width[i]==1) // hack to distinguish single width sequences.
1058 boolean sort = false;
1059 for (int i = 0; i < width.length; i++)
1061 // awidth = (float[]) typeWidth.get(data[i][0]);
1064 width[i] = fr.getOrder(data[i][TYPE_COLUMN].toString());
1067 width[i] = fr.setOrder(data[i][TYPE_COLUMN].toString(),
1073 width[i] /= max; // normalize
1074 fr.setOrder(data[i][TYPE_COLUMN].toString(), width[i]); // store for later
1078 sort = sort || width[i - 1] > width[i];
1083 jalview.util.QuickSort.sort(width, data);
1084 // update global priority order
1087 updateFeatureRenderer(data, false);
1095 frame.setClosed(true);
1096 } catch (Exception exe)
1102 public void updateFeatureRenderer(Object[][] data)
1104 updateFeatureRenderer(data, true);
1108 * Update the priority order of features; only repaint if this changed the order
1109 * of visible features
1114 private void updateFeatureRenderer(Object[][] data, boolean visibleNew)
1116 FeatureSettingsBean[] rowData = getTableAsBeans(data);
1118 if (fr.setFeaturePriority(rowData, visibleNew))
1120 af.alignPanel.paintAlignment(true, true);
1125 * Converts table data into an array of data beans
1127 private FeatureSettingsBean[] getTableAsBeans(Object[][] data)
1129 FeatureSettingsBean[] rowData = new FeatureSettingsBean[data.length];
1130 for (int i = 0; i < data.length; i++)
1132 String type = (String) data[i][TYPE_COLUMN];
1133 FeatureColourI colour = (FeatureColourI) data[i][COLOUR_COLUMN];
1134 FeatureMatcherSetI theFilter = (FeatureMatcherSetI) data[i][FILTER_COLUMN];
1135 Boolean isShown = (Boolean) data[i][SHOW_COLUMN];
1136 rowData[i] = new FeatureSettingsBean(type, colour, theFilter,
1142 private void jbInit() throws Exception
1144 this.setLayout(new BorderLayout());
1146 JPanel settingsPane = new JPanel();
1147 settingsPane.setLayout(new BorderLayout());
1149 JPanel bigPanel = new JPanel();
1150 bigPanel.setLayout(new BorderLayout());
1152 groupPanel = new JPanel();
1153 bigPanel.add(groupPanel, BorderLayout.NORTH);
1155 JButton invert = new JButton(
1156 MessageManager.getString("label.invert_selection"));
1157 invert.setFont(JvSwingUtils.getLabelFont());
1158 invert.addActionListener(new ActionListener()
1161 public void actionPerformed(ActionEvent e)
1167 JButton optimizeOrder = new JButton(
1168 MessageManager.getString("label.optimise_order"));
1169 optimizeOrder.setFont(JvSwingUtils.getLabelFont());
1170 optimizeOrder.addActionListener(new ActionListener()
1173 public void actionPerformed(ActionEvent e)
1179 JButton sortByScore = new JButton(
1180 MessageManager.getString("label.seq_sort_by_score"));
1181 sortByScore.setFont(JvSwingUtils.getLabelFont());
1182 sortByScore.addActionListener(new ActionListener()
1185 public void actionPerformed(ActionEvent e)
1187 af.avc.sortAlignmentByFeatureScore(null);
1190 JButton sortByDens = new JButton(
1191 MessageManager.getString("label.sequence_sort_by_density"));
1192 sortByDens.setFont(JvSwingUtils.getLabelFont());
1193 sortByDens.addActionListener(new ActionListener()
1196 public void actionPerformed(ActionEvent e)
1198 af.avc.sortAlignmentByFeatureDensity(null);
1202 JButton help = new JButton(MessageManager.getString("action.help"));
1203 help.setFont(JvSwingUtils.getLabelFont());
1204 help.addActionListener(new ActionListener()
1207 public void actionPerformed(ActionEvent e)
1211 Help.showHelpWindow(HelpId.SequenceFeatureSettings);
1212 } catch (HelpSetException e1)
1214 e1.printStackTrace();
1218 help.setFont(JvSwingUtils.getLabelFont());
1219 help.setText(MessageManager.getString("action.help"));
1220 help.addActionListener(new ActionListener()
1223 public void actionPerformed(ActionEvent e)
1227 Help.showHelpWindow(HelpId.SequenceFeatureSettings);
1228 } catch (HelpSetException e1)
1230 e1.printStackTrace();
1235 JButton cancel = new JButton(MessageManager.getString("action.cancel"));
1236 cancel.setFont(JvSwingUtils.getLabelFont());
1237 cancel.addActionListener(new ActionListener()
1240 public void actionPerformed(ActionEvent e)
1242 fr.setTransparency(originalTransparency);
1243 fr.setFeatureFilters(originalFilters);
1244 updateFeatureRenderer(originalData);
1249 JButton ok = new JButton(MessageManager.getString("action.ok"));
1250 ok.setFont(JvSwingUtils.getLabelFont());
1251 ok.addActionListener(new ActionListener()
1254 public void actionPerformed(ActionEvent e)
1260 JButton loadColours = new JButton(
1261 MessageManager.getString("label.load_colours"));
1262 loadColours.setFont(JvSwingUtils.getLabelFont());
1263 loadColours.setToolTipText(
1264 MessageManager.getString("label.load_colours_tooltip"));
1265 loadColours.addActionListener(new ActionListener()
1268 public void actionPerformed(ActionEvent e)
1274 JButton saveColours = new JButton(
1275 MessageManager.getString("label.save_colours"));
1276 saveColours.setFont(JvSwingUtils.getLabelFont());
1277 saveColours.setToolTipText(
1278 MessageManager.getString("label.save_colours_tooltip"));
1279 saveColours.addActionListener(new ActionListener()
1282 public void actionPerformed(ActionEvent e)
1287 transparency.addChangeListener(new ChangeListener()
1290 public void stateChanged(ChangeEvent evt)
1292 if (!inConstruction)
1294 fr.setTransparency((100 - transparency.getValue()) / 100f);
1295 af.alignPanel.paintAlignment(true, true);
1300 transparency.setMaximum(70);
1301 transparency.setToolTipText(
1302 MessageManager.getString("label.transparency_tip"));
1304 JPanel transPanel = new JPanel(new GridLayout(1, 2));
1305 bigPanel.add(transPanel, BorderLayout.SOUTH);
1307 JPanel transbuttons = new JPanel(new GridLayout(5, 1));
1308 transbuttons.add(optimizeOrder);
1309 transbuttons.add(invert);
1310 transbuttons.add(sortByScore);
1311 transbuttons.add(sortByDens);
1312 transbuttons.add(help);
1313 transPanel.add(transparency);
1314 transPanel.add(transbuttons);
1316 JPanel buttonPanel = new JPanel();
1317 buttonPanel.add(ok);
1318 buttonPanel.add(cancel);
1319 buttonPanel.add(loadColours);
1320 buttonPanel.add(saveColours);
1321 bigPanel.add(scrollPane, BorderLayout.CENTER);
1322 settingsPane.add(bigPanel, BorderLayout.CENTER);
1323 settingsPane.add(buttonPanel, BorderLayout.SOUTH);
1324 this.add(settingsPane);
1327 // ///////////////////////////////////////////////////////////////////////
1328 // http://java.sun.com/docs/books/tutorial/uiswing/components/table.html
1329 // ///////////////////////////////////////////////////////////////////////
1330 class FeatureTableModel extends AbstractTableModel
1332 private String[] columnNames = {
1333 MessageManager.getString("label.feature_type"),
1334 MessageManager.getString("action.colour"),
1335 MessageManager.getString("label.filter"),
1336 MessageManager.getString("label.show") };
1338 private Object[][] data;
1340 FeatureTableModel(Object[][] data)
1345 public Object[][] getData()
1350 public void setData(Object[][] data)
1356 public int getColumnCount()
1358 return columnNames.length;
1361 public Object[] getRow(int row)
1367 public int getRowCount()
1373 public String getColumnName(int col)
1375 return columnNames[col];
1379 public Object getValueAt(int row, int col)
1381 return data[row][col];
1385 * Answers the class of the object in column c of the first row of the table
1388 public Class<?> getColumnClass(int c)
1390 Object v = getValueAt(0, c);
1391 return v == null ? null : v.getClass();
1395 public boolean isCellEditable(int row, int col)
1397 return col == 0 ? false : true;
1401 public void setValueAt(Object value, int row, int col)
1403 data[row][col] = value;
1404 fireTableCellUpdated(row, col);
1405 updateFeatureRenderer(data);
1410 class ColorRenderer extends JLabel implements TableCellRenderer
1412 javax.swing.border.Border unselectedBorder = null;
1414 javax.swing.border.Border selectedBorder = null;
1416 final String baseTT = "Click to edit, right/apple click for menu.";
1418 public ColorRenderer()
1420 setOpaque(true); // MUST do this for background to show up.
1421 setHorizontalTextPosition(SwingConstants.CENTER);
1422 setVerticalTextPosition(SwingConstants.CENTER);
1426 public Component getTableCellRendererComponent(JTable tbl, Object color,
1427 boolean isSelected, boolean hasFocus, int row, int column)
1429 FeatureColourI cellColour = (FeatureColourI) color;
1431 setToolTipText(baseTT);
1432 setBackground(tbl.getBackground());
1433 if (!cellColour.isSimpleColour())
1435 Rectangle cr = tbl.getCellRect(row, column, false);
1436 FeatureSettings.renderGraduatedColor(this, cellColour,
1437 (int) cr.getWidth(), (int) cr.getHeight());
1443 setBackground(cellColour.getColour());
1447 if (selectedBorder == null)
1449 selectedBorder = BorderFactory.createMatteBorder(2, 5, 2, 5,
1450 tbl.getSelectionBackground());
1452 setBorder(selectedBorder);
1456 if (unselectedBorder == null)
1458 unselectedBorder = BorderFactory.createMatteBorder(2, 5, 2, 5,
1459 tbl.getBackground());
1461 setBorder(unselectedBorder);
1468 class FilterRenderer extends JLabel implements TableCellRenderer
1470 javax.swing.border.Border unselectedBorder = null;
1472 javax.swing.border.Border selectedBorder = null;
1474 public FilterRenderer()
1476 setOpaque(true); // MUST do this for background to show up.
1477 setHorizontalTextPosition(SwingConstants.CENTER);
1478 setVerticalTextPosition(SwingConstants.CENTER);
1482 public Component getTableCellRendererComponent(JTable tbl,
1483 Object filter, boolean isSelected, boolean hasFocus, int row,
1486 FeatureMatcherSetI theFilter = (FeatureMatcherSetI) filter;
1488 String asText = theFilter.toString();
1489 setBackground(tbl.getBackground());
1490 this.setText(asText);
1495 if (selectedBorder == null)
1497 selectedBorder = BorderFactory.createMatteBorder(2, 5, 2, 5,
1498 tbl.getSelectionBackground());
1500 setBorder(selectedBorder);
1504 if (unselectedBorder == null)
1506 unselectedBorder = BorderFactory.createMatteBorder(2, 5, 2, 5,
1507 tbl.getBackground());
1509 setBorder(unselectedBorder);
1517 * update comp using rendering settings from gcol
1522 public static void renderGraduatedColor(JLabel comp, FeatureColourI gcol)
1524 int w = comp.getWidth(), h = comp.getHeight();
1527 w = (int) comp.getPreferredSize().getWidth();
1528 h = (int) comp.getPreferredSize().getHeight();
1535 renderGraduatedColor(comp, gcol, w, h);
1538 public static void renderGraduatedColor(JLabel comp, FeatureColourI gcol,
1541 boolean thr = false;
1542 StringBuilder tt = new StringBuilder();
1543 StringBuilder tx = new StringBuilder();
1545 if (gcol.isColourByAttribute())
1547 tx.append(String.join(":", gcol.getAttributeName()));
1549 else if (!gcol.isColourByLabel())
1551 tx.append(MessageManager.getString("label.score"));
1554 if (gcol.isAboveThreshold())
1558 tt.append("Thresholded (Above ").append(gcol.getThreshold())
1561 if (gcol.isBelowThreshold())
1565 tt.append("Thresholded (Below ").append(gcol.getThreshold())
1568 if (gcol.isColourByLabel())
1570 tt.append("Coloured by label text. ").append(tt);
1575 if (!gcol.isColourByAttribute())
1583 Color newColor = gcol.getMaxColour();
1584 comp.setBackground(newColor);
1585 // System.err.println("Width is " + w / 2);
1586 Icon ficon = new FeatureIcon(gcol, comp.getBackground(), w, h, thr);
1587 comp.setIcon(ficon);
1588 // tt+="RGB value: Max (" + newColor.getRed() + ", "
1589 // + newColor.getGreen() + ", " + newColor.getBlue()
1590 // + ")\nMin (" + minCol.getRed() + ", " + minCol.getGreen()
1591 // + ", " + minCol.getBlue() + ")");
1593 comp.setHorizontalAlignment(SwingConstants.CENTER);
1594 comp.setText(tx.toString());
1595 if (tt.length() > 0)
1597 if (comp.getToolTipText() == null)
1599 comp.setToolTipText(tt.toString());
1603 comp.setToolTipText(
1604 tt.append(" ").append(comp.getToolTipText()).toString());
1609 class ColorEditor extends AbstractCellEditor
1610 implements TableCellEditor, ActionListener
1614 FeatureColourI currentColor;
1616 FeatureTypeSettings chooser;
1622 JColorChooser colorChooser;
1626 protected static final String EDIT = "edit";
1628 int rowSelected = 0;
1630 public ColorEditor(FeatureSettings me)
1633 // Set up the editor (from the table's point of view),
1634 // which is a button.
1635 // This button brings up the color chooser dialog,
1636 // which is the editor from the user's point of view.
1637 button = new JButton();
1638 button.setActionCommand(EDIT);
1639 button.addActionListener(this);
1640 button.setBorderPainted(false);
1641 // Set up the dialog that the button brings up.
1642 colorChooser = new JColorChooser();
1643 dialog = JColorChooser.createDialog(button,
1644 MessageManager.getString("label.select_colour"), true, // modal
1645 colorChooser, this, // OK button handler
1646 null); // no CANCEL button handler
1650 * Handles events from the editor button and from the dialog's OK button.
1653 public void actionPerformed(ActionEvent e)
1655 // todo test e.getSource() instead here
1656 if (EDIT.equals(e.getActionCommand()))
1658 // The user has clicked the cell, so
1659 // bring up the dialog.
1660 if (currentColor.isSimpleColour())
1662 // bring up simple color chooser
1663 button.setBackground(currentColor.getColour());
1664 colorChooser.setColor(currentColor.getColour());
1665 dialog.setVisible(true);
1669 // bring up graduated chooser.
1670 chooser = new FeatureTypeSettings(me.fr, type);
1675 chooser.setRequestFocusEnabled(true);
1676 chooser.requestFocus();
1678 chooser.addActionListener(this);
1679 // Make the renderer reappear.
1680 fireEditingStopped();
1685 if (currentColor.isSimpleColour())
1688 * read off colour picked in colour chooser after OK pressed
1690 currentColor = new FeatureColour(colorChooser.getColor());
1691 me.table.setValueAt(currentColor, rowSelected, COLOUR_COLUMN);
1696 * after OK in variable colour dialog, any changes to colour
1697 * (or filters!) are already set in FeatureRenderer, so just
1698 * update table data without triggering updateFeatureRenderer
1700 currentColor = fr.getFeatureColours().get(type);
1701 FeatureMatcherSetI currentFilter = me.fr.getFeatureFilter(type);
1702 if (currentFilter == null)
1704 currentFilter = new FeatureMatcherSet();
1706 Object[] data = ((FeatureTableModel) table.getModel())
1707 .getData()[rowSelected];
1708 data[COLOUR_COLUMN] = currentColor;
1709 data[FILTER_COLUMN] = currentFilter;
1711 fireEditingStopped();
1712 me.table.validate();
1716 // Implement the one CellEditor method that AbstractCellEditor doesn't.
1718 public Object getCellEditorValue()
1720 return currentColor;
1723 // Implement the one method defined by TableCellEditor.
1725 public Component getTableCellEditorComponent(JTable theTable, Object value,
1726 boolean isSelected, int row, int column)
1728 currentColor = (FeatureColourI) value;
1729 this.rowSelected = row;
1730 type = me.table.getValueAt(row, TYPE_COLUMN).toString();
1731 button.setOpaque(true);
1732 button.setBackground(me.getBackground());
1733 if (!currentColor.isSimpleColour())
1735 JLabel btn = new JLabel();
1736 btn.setSize(button.getSize());
1737 FeatureSettings.renderGraduatedColor(btn, currentColor);
1738 button.setBackground(btn.getBackground());
1739 button.setIcon(btn.getIcon());
1740 button.setText(btn.getText());
1745 button.setIcon(null);
1746 button.setBackground(currentColor.getColour());
1753 * The cell editor for the Filter column. It displays the text of any filters
1754 * for the feature type in that row (in full as a tooltip, possible abbreviated
1755 * as display text). On click in the cell, opens the Feature Display Settings
1756 * dialog at the Filters tab.
1758 class FilterEditor extends AbstractCellEditor
1759 implements TableCellEditor, ActionListener
1763 FeatureMatcherSetI currentFilter;
1771 protected static final String EDIT = "edit";
1773 int rowSelected = 0;
1775 public FilterEditor(FeatureSettings me)
1778 button = new JButton();
1779 button.setActionCommand(EDIT);
1780 button.addActionListener(this);
1781 button.setBorderPainted(false);
1785 * Handles events from the editor button
1788 public void actionPerformed(ActionEvent e)
1790 if (button == e.getSource())
1792 FeatureTypeSettings chooser = new FeatureTypeSettings(me.fr, type);
1793 chooser.addActionListener(this);
1794 chooser.setRequestFocusEnabled(true);
1795 chooser.requestFocus();
1796 if (lastLocation != null)
1798 // todo open at its last position on screen
1799 chooser.setBounds(lastLocation.x, lastLocation.y,
1800 chooser.getWidth(), chooser.getHeight());
1803 fireEditingStopped();
1805 else if (e.getSource() instanceof Component)
1809 * after OK in variable colour dialog, any changes to filter
1810 * (or colours!) are already set in FeatureRenderer, so just
1811 * update table data without triggering updateFeatureRenderer
1813 FeatureColourI currentColor = fr.getFeatureColours().get(type);
1814 currentFilter = me.fr.getFeatureFilter(type);
1815 if (currentFilter == null)
1817 currentFilter = new FeatureMatcherSet();
1819 Object[] data = ((FeatureTableModel) table.getModel())
1820 .getData()[rowSelected];
1821 data[COLOUR_COLUMN] = currentColor;
1822 data[FILTER_COLUMN] = currentFilter;
1823 fireEditingStopped();
1824 me.table.validate();
1829 public Object getCellEditorValue()
1831 return currentFilter;
1835 public Component getTableCellEditorComponent(JTable theTable, Object value,
1836 boolean isSelected, int row, int column)
1838 currentFilter = (FeatureMatcherSetI) value;
1839 this.rowSelected = row;
1840 type = me.table.getValueAt(row, TYPE_COLUMN).toString();
1841 button.setOpaque(true);
1842 button.setBackground(me.getBackground());
1843 button.setText(currentFilter.toString());
1844 button.setToolTipText(currentFilter.toString());
1845 button.setIcon(null);
1851 class FeatureIcon implements Icon
1853 FeatureColourI gcol;
1857 boolean midspace = false;
1859 int width = 50, height = 20;
1861 int s1, e1; // start and end of midpoint band for thresholded symbol
1863 Color mpcolour = Color.white;
1865 FeatureIcon(FeatureColourI gfc, Color bg, int w, int h, boolean mspace)
1885 public int getIconWidth()
1891 public int getIconHeight()
1897 public void paintIcon(Component c, Graphics g, int x, int y)
1900 if (gcol.isColourByLabel())
1903 g.fillRect(0, 0, width, height);
1904 // need an icon here.
1905 g.setColor(gcol.getMaxColour());
1907 g.setFont(new Font("Verdana", Font.PLAIN, 9));
1909 // g.setFont(g.getFont().deriveFont(
1910 // AffineTransform.getScaleInstance(
1911 // width/g.getFontMetrics().stringWidth("Label"),
1912 // height/g.getFontMetrics().getHeight())));
1914 g.drawString(MessageManager.getString("label.label"), 0, 0);
1919 Color minCol = gcol.getMinColour();
1921 g.fillRect(0, 0, s1, height);
1924 g.setColor(Color.white);
1925 g.fillRect(s1, 0, e1 - s1, height);
1927 g.setColor(gcol.getMaxColour());
1928 g.fillRect(0, e1, width - e1, height);