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.ArrayList;
66 import java.util.Arrays;
67 import java.util.Comparator;
68 import java.util.HashMap;
69 import java.util.HashSet;
70 import java.util.Hashtable;
71 import java.util.Iterator;
72 import java.util.List;
76 import javax.help.HelpSetException;
77 import javax.swing.AbstractCellEditor;
78 import javax.swing.BorderFactory;
79 import javax.swing.Icon;
80 import javax.swing.JButton;
81 import javax.swing.JCheckBox;
82 import javax.swing.JCheckBoxMenuItem;
83 import javax.swing.JColorChooser;
84 import javax.swing.JDialog;
85 import javax.swing.JInternalFrame;
86 import javax.swing.JLabel;
87 import javax.swing.JLayeredPane;
88 import javax.swing.JMenuItem;
89 import javax.swing.JPanel;
90 import javax.swing.JPopupMenu;
91 import javax.swing.JScrollPane;
92 import javax.swing.JSlider;
93 import javax.swing.JTable;
94 import javax.swing.ListSelectionModel;
95 import javax.swing.SwingConstants;
96 import javax.swing.event.ChangeEvent;
97 import javax.swing.event.ChangeListener;
98 import javax.swing.table.AbstractTableModel;
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 private List<String> originalVisibleGroups;
141 private List<String> originalHiddenGroups;
143 final JInternalFrame frame;
145 JScrollPane scrollPane = new JScrollPane();
151 JSlider transparency = new JSlider();
154 * when true, constructor is still executing - so ignore UI events
156 protected volatile boolean inConstruction = true;
158 int selectedRow = -1;
160 JButton fetchDAS = new JButton();
162 JButton saveDAS = new JButton();
164 JButton cancelDAS = new JButton();
166 boolean resettingTable = false;
169 * true when Feature Settings are updating from feature renderer
171 private boolean handlingUpdate = false;
174 * holds {featureCount, totalExtent} for each feature type
176 Map<String, float[]> typeWidth = null;
183 public FeatureSettings(AlignFrame alignFrame)
185 this.af = alignFrame;
186 fr = af.getFeatureRenderer();
188 // save transparency for restore on Cancel
189 originalTransparency = fr.getTransparency();
190 int originalTransparencyAsPercent = (int) (originalTransparency * 100);
191 transparency.setMaximum(100 - originalTransparencyAsPercent);
193 originalFilters = new HashMap<>(fr.getFeatureFilters()); // shallow copy
195 originalVisibleGroups = new ArrayList<>(fr.getGroups(true));
197 originalHiddenGroups = new ArrayList<>(fr.getGroups(false));
202 } catch (Exception ex)
204 ex.printStackTrace();
210 public String getToolTipText(MouseEvent e)
213 int column = table.columnAtPoint(e.getPoint());
217 tip = JvSwingUtils.wrapTooltip(true, MessageManager
218 .getString("label.feature_settings_click_drag"));
221 int row = table.rowAtPoint(e.getPoint());
222 FeatureMatcherSet o = (FeatureMatcherSet) table.getValueAt(row,
225 ? MessageManager.getString("label.filters_tooltip")
234 table.getTableHeader().setFont(new Font("Verdana", Font.PLAIN, 12));
235 table.setFont(new Font("Verdana", Font.PLAIN, 12));
237 // table.setDefaultRenderer(Color.class, new ColorRenderer());
238 // table.setDefaultEditor(Color.class, new ColorEditor(this));
240 table.setDefaultEditor(FeatureColour.class, new ColorEditor(this));
241 table.setDefaultRenderer(FeatureColour.class, new ColorRenderer());
243 table.setDefaultEditor(FeatureMatcherSet.class, new FilterEditor(this));
244 table.setDefaultRenderer(FeatureMatcherSet.class, new FilterRenderer());
246 TableColumn colourColumn = new TableColumn(COLOUR_COLUMN, 75,
247 new ColorRenderer(), new ColorEditor(this));
248 table.addColumn(colourColumn);
250 TableColumn filterColumn = new TableColumn(FILTER_COLUMN, 75,
251 new FilterRenderer(), new FilterEditor(this));
252 table.addColumn(filterColumn);
254 table.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
256 table.addMouseListener(new MouseAdapter()
259 public void mousePressed(MouseEvent evt)
261 selectedRow = table.rowAtPoint(evt.getPoint());
262 String type = (String) table.getValueAt(selectedRow, TYPE_COLUMN);
263 if (evt.isPopupTrigger())
265 Object colour = table.getValueAt(selectedRow, COLOUR_COLUMN);
266 popupSort(selectedRow, type, colour, fr.getMinMax(), evt.getX(),
269 else if (evt.getClickCount() == 2)
271 boolean invertSelection = evt.isAltDown();
272 boolean toggleSelection = Platform.isControlDown(evt);
273 boolean extendSelection = evt.isShiftDown();
274 fr.ap.alignFrame.avc.markColumnsContainingFeatures(
275 invertSelection, extendSelection, toggleSelection, type);
279 // isPopupTrigger fires on mouseReleased on Windows
281 public void mouseReleased(MouseEvent evt)
283 selectedRow = table.rowAtPoint(evt.getPoint());
284 if (evt.isPopupTrigger())
286 String type = (String) table.getValueAt(selectedRow, TYPE_COLUMN);
287 Object colour = table.getValueAt(selectedRow, COLOUR_COLUMN);
288 popupSort(selectedRow, type, colour, fr.getMinMax(), evt.getX(),
294 table.addMouseMotionListener(new MouseMotionAdapter()
297 public void mouseDragged(MouseEvent evt)
299 int newRow = table.rowAtPoint(evt.getPoint());
300 if (newRow != selectedRow && selectedRow != -1 && newRow != -1)
303 * reposition 'selectedRow' to 'newRow' (the dragged to location)
304 * this could be more than one row away for a very fast drag action
305 * so just swap it with adjacent rows until we get it there
307 Object[][] data = ((FeatureTableModel) table.getModel())
309 int direction = newRow < selectedRow ? -1 : 1;
310 for (int i = selectedRow; i != newRow; i += direction)
312 Object[] temp = data[i];
313 data[i] = data[i + direction];
314 data[i + direction] = temp;
316 updateFeatureRenderer(data);
318 selectedRow = newRow;
322 // table.setToolTipText(JvSwingUtils.wrapTooltip(true,
323 // MessageManager.getString("label.feature_settings_click_drag")));
324 scrollPane.setViewportView(table);
326 if (af.getViewport().isShowSequenceFeatures() || !fr.hasRenderOrder())
328 fr.findAllFeatures(true); // display everything!
331 discoverAllFeatureData();
332 final PropertyChangeListener change;
333 final FeatureSettings fs = this;
334 fr.addPropertyChangeListener(change = new PropertyChangeListener()
337 public void propertyChange(PropertyChangeEvent evt)
339 if (!fs.resettingTable && !fs.handlingUpdate)
341 fs.handlingUpdate = true;
343 // new groups may be added with new sequence feature types only
344 fs.handlingUpdate = false;
350 frame = new JInternalFrame();
351 frame.setContentPane(this);
352 if (Platform.isAMac())
354 Desktop.addInternalFrame(frame,
355 MessageManager.getString("label.sequence_feature_settings"),
360 Desktop.addInternalFrame(frame,
361 MessageManager.getString("label.sequence_feature_settings"),
364 frame.setMinimumSize(new Dimension(MIN_WIDTH, MIN_HEIGHT));
366 frame.addInternalFrameListener(
367 new javax.swing.event.InternalFrameAdapter()
370 public void internalFrameClosed(
371 javax.swing.event.InternalFrameEvent evt)
373 fr.removePropertyChangeListener(change);
376 frame.setLayer(JLayeredPane.PALETTE_LAYER);
377 inConstruction = false;
380 protected void popupSort(final int rowSelected, final String type,
381 final Object typeCol, final Map<String, float[][]> minmax, int x,
384 final FeatureColourI featureColour = (FeatureColourI) typeCol;
386 JPopupMenu men = new JPopupMenu(MessageManager
387 .formatMessage("label.settings_for_param", new String[]
389 JMenuItem scr = new JMenuItem(
390 MessageManager.getString("label.sort_by_score"));
392 final FeatureSettings me = this;
393 scr.addActionListener(new ActionListener()
397 public void actionPerformed(ActionEvent e)
400 .sortAlignmentByFeatureScore(Arrays.asList(new String[]
405 JMenuItem dens = new JMenuItem(
406 MessageManager.getString("label.sort_by_density"));
407 dens.addActionListener(new ActionListener()
411 public void actionPerformed(ActionEvent e)
414 .sortAlignmentByFeatureDensity(Arrays.asList(new String[]
422 * variable colour options include colour by label, by score,
423 * by selected attribute text, or attribute value
425 final JCheckBoxMenuItem mxcol = new JCheckBoxMenuItem(
426 MessageManager.getString("label.variable_colour"));
427 mxcol.setSelected(!featureColour.isSimpleColour());
429 mxcol.addActionListener(new ActionListener()
431 JColorChooser colorChooser;
434 public void actionPerformed(ActionEvent e)
436 if (e.getSource() == mxcol)
438 if (featureColour.isSimpleColour())
440 FeatureTypeSettings fc = new FeatureTypeSettings(me.fr, type);
441 fc.addActionListener(this);
445 // bring up simple color chooser
446 colorChooser = new JColorChooser();
447 String title = MessageManager
448 .getString("label.select_colour");
449 JDialog dialog = JColorChooser.createDialog(me,
450 title, true, // modal
451 colorChooser, this, // OK button handler
452 null); // no CANCEL button handler
453 colorChooser.setColor(featureColour.getMaxColour());
454 dialog.setVisible(true);
459 if (e.getSource() instanceof FeatureTypeSettings)
462 * update after OK in feature colour dialog; the updated
463 * colour will have already been set in the FeatureRenderer
465 FeatureColourI fci = fr.getFeatureColours().get(type);
466 table.setValueAt(fci, rowSelected, 1);
471 // probably the color chooser!
472 table.setValueAt(new FeatureColour(colorChooser.getColor()),
475 me.updateFeatureRenderer(
476 ((FeatureTableModel) table.getModel()).getData(),
484 JMenuItem selCols = new JMenuItem(
485 MessageManager.getString("label.select_columns_containing"));
486 selCols.addActionListener(new ActionListener()
489 public void actionPerformed(ActionEvent arg0)
491 fr.ap.alignFrame.avc.markColumnsContainingFeatures(false, false,
495 JMenuItem clearCols = new JMenuItem(MessageManager
496 .getString("label.select_columns_not_containing"));
497 clearCols.addActionListener(new ActionListener()
500 public void actionPerformed(ActionEvent arg0)
502 fr.ap.alignFrame.avc.markColumnsContainingFeatures(true, false,
506 JMenuItem hideCols = new JMenuItem(
507 MessageManager.getString("label.hide_columns_containing"));
508 hideCols.addActionListener(new ActionListener()
511 public void actionPerformed(ActionEvent arg0)
513 fr.ap.alignFrame.hideFeatureColumns(type, true);
516 JMenuItem hideOtherCols = new JMenuItem(
517 MessageManager.getString("label.hide_columns_not_containing"));
518 hideOtherCols.addActionListener(new ActionListener()
521 public void actionPerformed(ActionEvent arg0)
523 fr.ap.alignFrame.hideFeatureColumns(type, false);
529 men.add(hideOtherCols);
530 men.show(table, x, y);
534 synchronized public void discoverAllFeatureData()
536 Set<String> allGroups = new HashSet<>();
537 AlignmentI alignment = af.getViewport().getAlignment();
539 for (int i = 0; i < alignment.getHeight(); i++)
541 SequenceI seq = alignment.getSequenceAt(i);
542 for (String group : seq.getFeatures().getFeatureGroups(true))
544 if (group != null && !allGroups.contains(group))
546 allGroups.add(group);
547 checkGroupState(group);
558 * Synchronise gui group list and check visibility of group
561 * @return true if group is visible
563 private boolean checkGroupState(String group)
565 boolean visible = fr.checkGroupVisibility(group, true);
567 for (int g = 0; g < groupPanel.getComponentCount(); g++)
569 if (((JCheckBox) groupPanel.getComponent(g)).getText().equals(group))
571 ((JCheckBox) groupPanel.getComponent(g)).setSelected(visible);
576 final String grp = group;
577 final JCheckBox check = new JCheckBox(group, visible);
578 check.setFont(new Font("Serif", Font.BOLD, 12));
579 check.setToolTipText(group);
580 check.addItemListener(new ItemListener()
583 public void itemStateChanged(ItemEvent evt)
585 fr.setGroupVisibility(check.getText(), check.isSelected());
586 resetTable(new String[] { grp });
587 af.alignPanel.paintAlignment(true, true);
590 groupPanel.add(check);
594 synchronized void resetTable(String[] groupChanged)
600 resettingTable = true;
601 typeWidth = new Hashtable<>();
602 // TODO: change avWidth calculation to 'per-sequence' average and use long
605 Set<String> displayableTypes = new HashSet<>();
606 Set<String> foundGroups = new HashSet<>();
609 * determine which feature types may be visible depending on
610 * which groups are selected, and recompute average width data
612 for (int i = 0; i < af.getViewport().getAlignment().getHeight(); i++)
615 SequenceI seq = af.getViewport().getAlignment().getSequenceAt(i);
618 * get the sequence's groups for positional features
619 * and keep track of which groups are visible
621 Set<String> groups = seq.getFeatures().getFeatureGroups(true);
622 Set<String> visibleGroups = new HashSet<>();
623 for (String group : groups)
625 if (group == null || checkGroupState(group))
627 visibleGroups.add(group);
630 foundGroups.addAll(groups);
633 * get distinct feature types for visible groups
634 * record distinct visible types, and their count and total length
636 Set<String> types = seq.getFeatures().getFeatureTypesForGroups(true,
637 visibleGroups.toArray(new String[visibleGroups.size()]));
638 for (String type : types)
640 displayableTypes.add(type);
641 float[] avWidth = typeWidth.get(type);
644 avWidth = new float[2];
645 typeWidth.put(type, avWidth);
647 // todo this could include features with a non-visible group
648 // - do we greatly care?
649 // todo should we include non-displayable features here, and only
650 // update when features are added?
651 avWidth[0] += seq.getFeatures().getFeatureCount(true, type);
652 avWidth[1] += seq.getFeatures().getTotalFeatureLength(type);
656 Object[][] data = new Object[displayableTypes.size()][COLUMN_COUNT];
659 if (fr.hasRenderOrder())
663 fr.findAllFeatures(groupChanged != null); // prod to update
664 // colourschemes. but don't
666 // First add the checks in the previous render order,
667 // in case the window has been closed and reopened
669 List<String> frl = fr.getRenderOrder();
670 for (int ro = frl.size() - 1; ro > -1; ro--)
672 String type = frl.get(ro);
674 if (!displayableTypes.contains(type))
679 data[dataIndex][TYPE_COLUMN] = type;
680 data[dataIndex][COLOUR_COLUMN] = fr.getFeatureStyle(type);
681 FeatureMatcherSetI featureFilter = fr.getFeatureFilter(type);
682 data[dataIndex][FILTER_COLUMN] = featureFilter == null
683 ? new FeatureMatcherSet()
685 data[dataIndex][SHOW_COLUMN] = new Boolean(
686 af.getViewport().getFeaturesDisplayed().isVisible(type));
688 displayableTypes.remove(type);
693 * process any extra features belonging only to
694 * a group which was just selected
696 while (!displayableTypes.isEmpty())
698 String type = displayableTypes.iterator().next();
699 data[dataIndex][TYPE_COLUMN] = type;
701 data[dataIndex][COLOUR_COLUMN] = fr.getFeatureStyle(type);
702 if (data[dataIndex][COLOUR_COLUMN] == null)
704 // "Colour has been updated in another view!!"
705 fr.clearRenderOrder();
708 FeatureMatcherSetI featureFilter = fr.getFeatureFilter(type);
709 data[dataIndex][FILTER_COLUMN] = featureFilter == null
710 ? new FeatureMatcherSet()
712 data[dataIndex][SHOW_COLUMN] = new Boolean(true);
714 displayableTypes.remove(type);
717 if (originalData == null)
719 originalData = new Object[data.length][COLUMN_COUNT];
720 for (int i = 0; i < data.length; i++)
722 System.arraycopy(data[i], 0, originalData[i], 0, COLUMN_COUNT);
727 updateOriginalData(data);
730 table.setModel(new FeatureTableModel(data));
731 table.getColumnModel().getColumn(0).setPreferredWidth(200);
733 groupPanel.setLayout(
734 new GridLayout(fr.getFeatureGroupsSize() / 4 + 1, 4));
735 pruneGroups(foundGroups);
736 groupPanel.validate();
738 updateFeatureRenderer(data, groupChanged != null);
739 resettingTable = false;
743 * Updates 'originalData' (used for restore on Cancel) if we detect that changes
744 * have been made outwith this dialog
746 * <li>a new feature type added (and made visible)</li>
747 * <li>a feature colour changed (in the Amend Features dialog)</li>
752 protected void updateOriginalData(Object[][] foundData)
754 // todo LinkedHashMap instead of Object[][] would be nice
756 Object[][] currentData = ((FeatureTableModel) table.getModel())
758 for (Object[] row : foundData)
760 String type = (String) row[TYPE_COLUMN];
761 boolean found = false;
762 for (Object[] current : currentData)
764 if (type.equals(current[TYPE_COLUMN]))
768 * currently dependent on object equality here;
769 * really need an equals method on FeatureColour
771 if (!row[COLOUR_COLUMN].equals(current[COLOUR_COLUMN]))
774 * feature colour has changed externally - update originalData
776 for (Object[] original : originalData)
778 if (type.equals(original[TYPE_COLUMN]))
780 original[COLOUR_COLUMN] = row[COLOUR_COLUMN];
791 * new feature detected - add to original data (on top)
793 Object[][] newData = new Object[originalData.length
795 for (int i = 0; i < originalData.length; i++)
797 System.arraycopy(originalData[i], 0, newData[i + 1], 0,
801 originalData = newData;
807 * Remove from the groups panel any checkboxes for groups that are not in the
808 * foundGroups set. This enables removing a group from the display when the last
809 * feature in that group is deleted.
813 protected void pruneGroups(Set<String> foundGroups)
815 for (int g = 0; g < groupPanel.getComponentCount(); g++)
817 JCheckBox checkbox = (JCheckBox) groupPanel.getComponent(g);
818 if (!foundGroups.contains(checkbox.getText()))
820 groupPanel.remove(checkbox);
826 * reorder data based on the featureRenderers global priority list.
830 private void ensureOrder(Object[][] data)
832 boolean sort = false;
833 float[] order = new float[data.length];
834 for (int i = 0; i < order.length; i++)
836 order[i] = fr.getOrder(data[i][0].toString());
839 order[i] = fr.setOrder(data[i][0].toString(), i / order.length);
843 sort = sort || order[i - 1] > order[i];
848 jalview.util.QuickSort.sort(order, data);
853 * Offers a file chooser dialog, and then loads the feature colours and
854 * filters from file in XML format and unmarshals to Jalview feature settings
858 JalviewFileChooser chooser = new JalviewFileChooser("fc",
859 SEQUENCE_FEATURE_COLOURS);
860 chooser.setFileView(new JalviewFileView());
861 chooser.setDialogTitle(
862 MessageManager.getString("label.load_feature_colours"));
863 chooser.setToolTipText(MessageManager.getString("action.load"));
865 int value = chooser.showOpenDialog(this);
867 if (value == JalviewFileChooser.APPROVE_OPTION)
869 File file = chooser.getSelectedFile();
875 * Loads feature colours and filters from XML stored in the given file
883 InputStreamReader in = new InputStreamReader(
884 new FileInputStream(file), "UTF-8");
886 JalviewUserColours jucs = JalviewUserColours.unmarshal(in);
889 * load feature colours
891 for (int i = jucs.getColourCount() - 1; i >= 0; i--)
893 jalview.schemabinding.version2.Colour newcol = jucs.getColour(i);
894 FeatureColourI colour = Jalview2XML.unmarshalColour(newcol);
895 fr.setColour(newcol.getName(), colour);
896 fr.setOrder(newcol.getName(), i / (float) jucs.getColourCount());
900 * load feature filters; loaded filters will replace any that are
901 * currently defined, other defined filters are left unchanged
903 for (int i = 0; i < jucs.getFilterCount(); i++)
905 jalview.schemabinding.version2.Filter filterModel = jucs
907 String featureType = filterModel.getFeatureType();
908 FeatureMatcherSetI filter = Jalview2XML.unmarshalFilter(featureType,
909 filterModel.getMatcherSet());
910 if (!filter.isEmpty())
912 fr.setFeatureFilter(featureType, filter);
917 * update feature settings table
922 Object[][] data = ((FeatureTableModel) table.getModel())
925 updateFeatureRenderer(data, false);
928 } catch (Exception ex)
930 System.out.println("Error loading User Colour File\n" + ex);
935 * Offers a file chooser dialog, and then saves the current feature colours
936 * and any filters to the selected file in XML format
940 JalviewFileChooser chooser = new JalviewFileChooser("fc",
941 SEQUENCE_FEATURE_COLOURS);
942 chooser.setFileView(new JalviewFileView());
943 chooser.setDialogTitle(
944 MessageManager.getString("label.save_feature_colours"));
945 chooser.setToolTipText(MessageManager.getString("action.save"));
947 int value = chooser.showSaveDialog(this);
949 if (value == JalviewFileChooser.APPROVE_OPTION)
951 save(chooser.getSelectedFile());
956 * Saves feature colours and filters to the given file
962 JalviewUserColours ucs = new JalviewUserColours();
963 ucs.setSchemeName("Sequence Features");
966 PrintWriter out = new PrintWriter(new OutputStreamWriter(
967 new FileOutputStream(file), "UTF-8"));
970 * sort feature types by colour order, from 0 (highest)
973 Set<String> fr_colours = fr.getAllFeatureColours();
974 String[] sortedTypes = fr_colours
975 .toArray(new String[fr_colours.size()]);
976 Arrays.sort(sortedTypes, new Comparator<String>()
979 public int compare(String type1, String type2)
981 return Float.compare(fr.getOrder(type1), fr.getOrder(type2));
986 * save feature colours
988 for (String featureType : sortedTypes)
990 FeatureColourI fcol = fr.getFeatureStyle(featureType);
991 jalview.schemabinding.version2.Colour col = Jalview2XML.marshalColour(
997 * save any feature filters
999 for (String featureType : sortedTypes)
1001 FeatureMatcherSetI filter = fr.getFeatureFilter(featureType);
1002 if (filter != null && !filter.isEmpty())
1004 Iterator<FeatureMatcherI> iterator = filter.getMatchers().iterator();
1005 FeatureMatcherI firstMatcher = iterator.next();
1006 MatcherSet ms = Jalview2XML.marshalFilter(firstMatcher, iterator,
1008 Filter filterModel = new Filter();
1009 filterModel.setFeatureType(featureType);
1010 filterModel.setMatcherSet(ms);
1011 ucs.addFilter(filterModel);
1017 } catch (Exception ex)
1019 ex.printStackTrace();
1023 public void invertSelection()
1025 Object[][] data = ((FeatureTableModel) table.getModel()).getData();
1026 for (int i = 0; i < data.length; i++)
1028 data[i][SHOW_COLUMN] = !(Boolean) data[i][SHOW_COLUMN];
1030 updateFeatureRenderer(data, true);
1034 public void orderByAvWidth()
1036 if (table == null || table.getModel() == null)
1040 Object[][] data = ((FeatureTableModel) table.getModel()).getData();
1041 float[] width = new float[data.length];
1045 for (int i = 0; i < data.length; i++)
1047 awidth = typeWidth.get(data[i][TYPE_COLUMN]);
1050 width[i] = awidth[1] / awidth[0];// *awidth[0]*awidth[2]; - better
1051 // weight - but have to make per
1052 // sequence, too (awidth[2])
1053 // if (width[i]==1) // hack to distinguish single width sequences.
1064 boolean sort = false;
1065 for (int i = 0; i < width.length; i++)
1067 // awidth = (float[]) typeWidth.get(data[i][0]);
1070 width[i] = fr.getOrder(data[i][TYPE_COLUMN].toString());
1073 width[i] = fr.setOrder(data[i][TYPE_COLUMN].toString(),
1079 width[i] /= max; // normalize
1080 fr.setOrder(data[i][TYPE_COLUMN].toString(), width[i]); // store for later
1084 sort = sort || width[i - 1] > width[i];
1089 jalview.util.QuickSort.sort(width, data);
1090 // update global priority order
1093 updateFeatureRenderer(data, false);
1101 frame.setClosed(true);
1102 } catch (Exception exe)
1108 public void updateFeatureRenderer(Object[][] data)
1110 updateFeatureRenderer(data, true);
1114 * Update the priority order of features; only repaint if this changed the order
1115 * of visible features
1120 private void updateFeatureRenderer(Object[][] data, boolean visibleNew)
1122 FeatureSettingsBean[] rowData = getTableAsBeans(data);
1124 if (fr.setFeaturePriority(rowData, visibleNew))
1126 af.alignPanel.paintAlignment(true, true);
1131 * Converts table data into an array of data beans
1133 private FeatureSettingsBean[] getTableAsBeans(Object[][] data)
1135 FeatureSettingsBean[] rowData = new FeatureSettingsBean[data.length];
1136 for (int i = 0; i < data.length; i++)
1138 String type = (String) data[i][TYPE_COLUMN];
1139 FeatureColourI colour = (FeatureColourI) data[i][COLOUR_COLUMN];
1140 FeatureMatcherSetI theFilter = (FeatureMatcherSetI) data[i][FILTER_COLUMN];
1141 Boolean isShown = (Boolean) data[i][SHOW_COLUMN];
1142 rowData[i] = new FeatureSettingsBean(type, colour, theFilter,
1148 private void jbInit() throws Exception
1150 this.setLayout(new BorderLayout());
1152 JPanel settingsPane = new JPanel();
1153 settingsPane.setLayout(new BorderLayout());
1155 JPanel bigPanel = new JPanel();
1156 bigPanel.setLayout(new BorderLayout());
1158 groupPanel = new JPanel();
1159 bigPanel.add(groupPanel, BorderLayout.NORTH);
1161 JButton invert = new JButton(
1162 MessageManager.getString("label.invert_selection"));
1163 invert.setFont(JvSwingUtils.getLabelFont());
1164 invert.addActionListener(new ActionListener()
1167 public void actionPerformed(ActionEvent e)
1173 JButton optimizeOrder = new JButton(
1174 MessageManager.getString("label.optimise_order"));
1175 optimizeOrder.setFont(JvSwingUtils.getLabelFont());
1176 optimizeOrder.addActionListener(new ActionListener()
1179 public void actionPerformed(ActionEvent e)
1185 JButton sortByScore = new JButton(
1186 MessageManager.getString("label.seq_sort_by_score"));
1187 sortByScore.setFont(JvSwingUtils.getLabelFont());
1188 sortByScore.addActionListener(new ActionListener()
1191 public void actionPerformed(ActionEvent e)
1193 af.avc.sortAlignmentByFeatureScore(null);
1196 JButton sortByDens = new JButton(
1197 MessageManager.getString("label.sequence_sort_by_density"));
1198 sortByDens.setFont(JvSwingUtils.getLabelFont());
1199 sortByDens.addActionListener(new ActionListener()
1202 public void actionPerformed(ActionEvent e)
1204 af.avc.sortAlignmentByFeatureDensity(null);
1208 JButton help = new JButton(MessageManager.getString("action.help"));
1209 help.setFont(JvSwingUtils.getLabelFont());
1210 help.addActionListener(new ActionListener()
1213 public void actionPerformed(ActionEvent e)
1217 Help.showHelpWindow(HelpId.SequenceFeatureSettings);
1218 } catch (HelpSetException e1)
1220 e1.printStackTrace();
1224 help.setFont(JvSwingUtils.getLabelFont());
1225 help.setText(MessageManager.getString("action.help"));
1226 help.addActionListener(new ActionListener()
1229 public void actionPerformed(ActionEvent e)
1233 Help.showHelpWindow(HelpId.SequenceFeatureSettings);
1234 } catch (HelpSetException e1)
1236 e1.printStackTrace();
1241 JButton cancel = new JButton(MessageManager.getString("action.cancel"));
1242 cancel.setFont(JvSwingUtils.getLabelFont());
1243 cancel.addActionListener(new ActionListener()
1246 public void actionPerformed(ActionEvent e)
1252 JButton ok = new JButton(MessageManager.getString("action.ok"));
1253 ok.setFont(JvSwingUtils.getLabelFont());
1254 ok.addActionListener(new ActionListener()
1257 public void actionPerformed(ActionEvent e)
1263 JButton loadColours = new JButton(
1264 MessageManager.getString("label.load_colours"));
1265 loadColours.setFont(JvSwingUtils.getLabelFont());
1266 loadColours.setToolTipText(
1267 MessageManager.getString("label.load_colours_tooltip"));
1268 loadColours.addActionListener(new ActionListener()
1271 public void actionPerformed(ActionEvent e)
1277 JButton saveColours = new JButton(
1278 MessageManager.getString("label.save_colours"));
1279 saveColours.setFont(JvSwingUtils.getLabelFont());
1280 saveColours.setToolTipText(
1281 MessageManager.getString("label.save_colours_tooltip"));
1282 saveColours.addActionListener(new ActionListener()
1285 public void actionPerformed(ActionEvent e)
1290 transparency.addChangeListener(new ChangeListener()
1293 public void stateChanged(ChangeEvent evt)
1295 if (!inConstruction)
1297 fr.setTransparency((100 - transparency.getValue()) / 100f);
1298 af.alignPanel.paintAlignment(true, true);
1303 transparency.setMaximum(70);
1304 transparency.setToolTipText(
1305 MessageManager.getString("label.transparency_tip"));
1307 JPanel transPanel = new JPanel(new GridLayout(1, 2));
1308 bigPanel.add(transPanel, BorderLayout.SOUTH);
1310 JPanel transbuttons = new JPanel(new GridLayout(5, 1));
1311 transbuttons.add(optimizeOrder);
1312 transbuttons.add(invert);
1313 transbuttons.add(sortByScore);
1314 transbuttons.add(sortByDens);
1315 transbuttons.add(help);
1316 transPanel.add(transparency);
1317 transPanel.add(transbuttons);
1319 JPanel buttonPanel = new JPanel();
1320 buttonPanel.add(ok);
1321 buttonPanel.add(cancel);
1322 buttonPanel.add(loadColours);
1323 buttonPanel.add(saveColours);
1324 bigPanel.add(scrollPane, BorderLayout.CENTER);
1325 settingsPane.add(bigPanel, BorderLayout.CENTER);
1326 settingsPane.add(buttonPanel, BorderLayout.SOUTH);
1327 this.add(settingsPane);
1331 * Restores feature type and group visibility, and any filters and
1332 * transparency setting, to the values when this dialog was opened. Note this
1333 * won't affect any feature types or groups which were added while the dialog
1338 fr.setTransparency(originalTransparency);
1339 fr.setFeatureFilters(originalFilters);
1340 fr.setGroupVisibility(originalVisibleGroups, true);
1341 fr.setGroupVisibility(originalHiddenGroups, false);
1342 updateFeatureRenderer(originalData);
1346 // ///////////////////////////////////////////////////////////////////////
1347 // http://java.sun.com/docs/books/tutorial/uiswing/components/table.html
1348 // ///////////////////////////////////////////////////////////////////////
1349 class FeatureTableModel extends AbstractTableModel
1351 private String[] columnNames = {
1352 MessageManager.getString("label.feature_type"),
1353 MessageManager.getString("action.colour"),
1354 MessageManager.getString("label.filter"),
1355 MessageManager.getString("label.show") };
1357 private Object[][] data;
1359 FeatureTableModel(Object[][] data)
1364 public Object[][] getData()
1369 public void setData(Object[][] data)
1375 public int getColumnCount()
1377 return columnNames.length;
1380 public Object[] getRow(int row)
1386 public int getRowCount()
1392 public String getColumnName(int col)
1394 return columnNames[col];
1398 public Object getValueAt(int row, int col)
1400 return data[row][col];
1404 * Answers the class of the object in column c of the first row of the table
1407 public Class<?> getColumnClass(int c)
1409 Object v = getValueAt(0, c);
1410 return v == null ? null : v.getClass();
1414 public boolean isCellEditable(int row, int col)
1416 return col == 0 ? false : true;
1420 public void setValueAt(Object value, int row, int col)
1422 data[row][col] = value;
1423 fireTableCellUpdated(row, col);
1424 updateFeatureRenderer(data);
1429 class ColorRenderer extends JLabel implements TableCellRenderer
1431 javax.swing.border.Border unselectedBorder = null;
1433 javax.swing.border.Border selectedBorder = null;
1435 final String baseTT = "Click to edit, right/apple click for menu.";
1437 public ColorRenderer()
1439 setOpaque(true); // MUST do this for background to show up.
1440 setHorizontalTextPosition(SwingConstants.CENTER);
1441 setVerticalTextPosition(SwingConstants.CENTER);
1445 public Component getTableCellRendererComponent(JTable tbl, Object color,
1446 boolean isSelected, boolean hasFocus, int row, int column)
1448 FeatureColourI cellColour = (FeatureColourI) color;
1450 setToolTipText(baseTT);
1451 setBackground(tbl.getBackground());
1452 if (!cellColour.isSimpleColour())
1454 Rectangle cr = tbl.getCellRect(row, column, false);
1455 FeatureSettings.renderGraduatedColor(this, cellColour,
1456 (int) cr.getWidth(), (int) cr.getHeight());
1462 setBackground(cellColour.getColour());
1466 if (selectedBorder == null)
1468 selectedBorder = BorderFactory.createMatteBorder(2, 5, 2, 5,
1469 tbl.getSelectionBackground());
1471 setBorder(selectedBorder);
1475 if (unselectedBorder == null)
1477 unselectedBorder = BorderFactory.createMatteBorder(2, 5, 2, 5,
1478 tbl.getBackground());
1480 setBorder(unselectedBorder);
1487 class FilterRenderer extends JLabel implements TableCellRenderer
1489 javax.swing.border.Border unselectedBorder = null;
1491 javax.swing.border.Border selectedBorder = null;
1493 public FilterRenderer()
1495 setOpaque(true); // MUST do this for background to show up.
1496 setHorizontalTextPosition(SwingConstants.CENTER);
1497 setVerticalTextPosition(SwingConstants.CENTER);
1501 public Component getTableCellRendererComponent(JTable tbl,
1502 Object filter, boolean isSelected, boolean hasFocus, int row,
1505 FeatureMatcherSetI theFilter = (FeatureMatcherSetI) filter;
1507 String asText = theFilter.toString();
1508 setBackground(tbl.getBackground());
1509 this.setText(asText);
1514 if (selectedBorder == null)
1516 selectedBorder = BorderFactory.createMatteBorder(2, 5, 2, 5,
1517 tbl.getSelectionBackground());
1519 setBorder(selectedBorder);
1523 if (unselectedBorder == null)
1525 unselectedBorder = BorderFactory.createMatteBorder(2, 5, 2, 5,
1526 tbl.getBackground());
1528 setBorder(unselectedBorder);
1536 * update comp using rendering settings from gcol
1541 public static void renderGraduatedColor(JLabel comp, FeatureColourI gcol)
1543 int w = comp.getWidth(), h = comp.getHeight();
1546 w = (int) comp.getPreferredSize().getWidth();
1547 h = (int) comp.getPreferredSize().getHeight();
1554 renderGraduatedColor(comp, gcol, w, h);
1557 public static void renderGraduatedColor(JLabel comp, FeatureColourI gcol,
1560 boolean thr = false;
1561 StringBuilder tt = new StringBuilder();
1562 StringBuilder tx = new StringBuilder();
1564 if (gcol.isColourByAttribute())
1566 tx.append(String.join(":", gcol.getAttributeName()));
1568 else if (!gcol.isColourByLabel())
1570 tx.append(MessageManager.getString("label.score"));
1573 if (gcol.isAboveThreshold())
1577 tt.append("Thresholded (Above ").append(gcol.getThreshold())
1580 if (gcol.isBelowThreshold())
1584 tt.append("Thresholded (Below ").append(gcol.getThreshold())
1587 if (gcol.isColourByLabel())
1589 tt.append("Coloured by label text. ").append(tt);
1594 if (!gcol.isColourByAttribute())
1602 Color newColor = gcol.getMaxColour();
1603 comp.setBackground(newColor);
1604 // System.err.println("Width is " + w / 2);
1605 Icon ficon = new FeatureIcon(gcol, comp.getBackground(), w, h, thr);
1606 comp.setIcon(ficon);
1607 // tt+="RGB value: Max (" + newColor.getRed() + ", "
1608 // + newColor.getGreen() + ", " + newColor.getBlue()
1609 // + ")\nMin (" + minCol.getRed() + ", " + minCol.getGreen()
1610 // + ", " + minCol.getBlue() + ")");
1612 comp.setHorizontalAlignment(SwingConstants.CENTER);
1613 comp.setText(tx.toString());
1614 if (tt.length() > 0)
1616 if (comp.getToolTipText() == null)
1618 comp.setToolTipText(tt.toString());
1622 comp.setToolTipText(
1623 tt.append(" ").append(comp.getToolTipText()).toString());
1628 class ColorEditor extends AbstractCellEditor
1629 implements TableCellEditor, ActionListener
1633 FeatureColourI currentColor;
1635 FeatureTypeSettings chooser;
1641 JColorChooser colorChooser;
1645 protected static final String EDIT = "edit";
1647 int rowSelected = 0;
1649 public ColorEditor(FeatureSettings me)
1652 // Set up the editor (from the table's point of view),
1653 // which is a button.
1654 // This button brings up the color chooser dialog,
1655 // which is the editor from the user's point of view.
1656 button = new JButton();
1657 button.setActionCommand(EDIT);
1658 button.addActionListener(this);
1659 button.setBorderPainted(false);
1660 // Set up the dialog that the button brings up.
1661 colorChooser = new JColorChooser();
1662 dialog = JColorChooser.createDialog(button,
1663 MessageManager.getString("label.select_colour"), true, // modal
1664 colorChooser, this, // OK button handler
1665 null); // no CANCEL button handler
1669 * Handles events from the editor button and from the dialog's OK button.
1672 public void actionPerformed(ActionEvent e)
1674 // todo test e.getSource() instead here
1675 if (EDIT.equals(e.getActionCommand()))
1677 // The user has clicked the cell, so
1678 // bring up the dialog.
1679 if (currentColor.isSimpleColour())
1681 // bring up simple color chooser
1682 button.setBackground(currentColor.getColour());
1683 colorChooser.setColor(currentColor.getColour());
1684 dialog.setVisible(true);
1688 // bring up graduated chooser.
1689 chooser = new FeatureTypeSettings(me.fr, type);
1694 chooser.setRequestFocusEnabled(true);
1695 chooser.requestFocus();
1697 chooser.addActionListener(this);
1698 // Make the renderer reappear.
1699 fireEditingStopped();
1704 if (currentColor.isSimpleColour())
1707 * read off colour picked in colour chooser after OK pressed
1709 currentColor = new FeatureColour(colorChooser.getColor());
1710 me.table.setValueAt(currentColor, rowSelected, COLOUR_COLUMN);
1715 * after OK in variable colour dialog, any changes to colour
1716 * (or filters!) are already set in FeatureRenderer, so just
1717 * update table data without triggering updateFeatureRenderer
1719 currentColor = fr.getFeatureColours().get(type);
1720 FeatureMatcherSetI currentFilter = me.fr.getFeatureFilter(type);
1721 if (currentFilter == null)
1723 currentFilter = new FeatureMatcherSet();
1725 Object[] data = ((FeatureTableModel) table.getModel())
1726 .getData()[rowSelected];
1727 data[COLOUR_COLUMN] = currentColor;
1728 data[FILTER_COLUMN] = currentFilter;
1730 fireEditingStopped();
1731 me.table.validate();
1735 // Implement the one CellEditor method that AbstractCellEditor doesn't.
1737 public Object getCellEditorValue()
1739 return currentColor;
1742 // Implement the one method defined by TableCellEditor.
1744 public Component getTableCellEditorComponent(JTable theTable, Object value,
1745 boolean isSelected, int row, int column)
1747 currentColor = (FeatureColourI) value;
1748 this.rowSelected = row;
1749 type = me.table.getValueAt(row, TYPE_COLUMN).toString();
1750 button.setOpaque(true);
1751 button.setBackground(me.getBackground());
1752 if (!currentColor.isSimpleColour())
1754 JLabel btn = new JLabel();
1755 btn.setSize(button.getSize());
1756 FeatureSettings.renderGraduatedColor(btn, currentColor);
1757 button.setBackground(btn.getBackground());
1758 button.setIcon(btn.getIcon());
1759 button.setText(btn.getText());
1764 button.setIcon(null);
1765 button.setBackground(currentColor.getColour());
1772 * The cell editor for the Filter column. It displays the text of any filters
1773 * for the feature type in that row (in full as a tooltip, possible abbreviated
1774 * as display text). On click in the cell, opens the Feature Display Settings
1775 * dialog at the Filters tab.
1777 class FilterEditor extends AbstractCellEditor
1778 implements TableCellEditor, ActionListener
1782 FeatureMatcherSetI currentFilter;
1790 protected static final String EDIT = "edit";
1792 int rowSelected = 0;
1794 public FilterEditor(FeatureSettings me)
1797 button = new JButton();
1798 button.setActionCommand(EDIT);
1799 button.addActionListener(this);
1800 button.setBorderPainted(false);
1804 * Handles events from the editor button
1807 public void actionPerformed(ActionEvent e)
1809 if (button == e.getSource())
1811 FeatureTypeSettings chooser = new FeatureTypeSettings(me.fr, type);
1812 chooser.addActionListener(this);
1813 chooser.setRequestFocusEnabled(true);
1814 chooser.requestFocus();
1815 if (lastLocation != null)
1817 // todo open at its last position on screen
1818 chooser.setBounds(lastLocation.x, lastLocation.y,
1819 chooser.getWidth(), chooser.getHeight());
1822 fireEditingStopped();
1824 else if (e.getSource() instanceof Component)
1828 * after OK in variable colour dialog, any changes to filter
1829 * (or colours!) are already set in FeatureRenderer, so just
1830 * update table data without triggering updateFeatureRenderer
1832 FeatureColourI currentColor = fr.getFeatureColours().get(type);
1833 currentFilter = me.fr.getFeatureFilter(type);
1834 if (currentFilter == null)
1836 currentFilter = new FeatureMatcherSet();
1838 Object[] data = ((FeatureTableModel) table.getModel())
1839 .getData()[rowSelected];
1840 data[COLOUR_COLUMN] = currentColor;
1841 data[FILTER_COLUMN] = currentFilter;
1842 fireEditingStopped();
1843 me.table.validate();
1848 public Object getCellEditorValue()
1850 return currentFilter;
1854 public Component getTableCellEditorComponent(JTable theTable, Object value,
1855 boolean isSelected, int row, int column)
1857 currentFilter = (FeatureMatcherSetI) value;
1858 this.rowSelected = row;
1859 type = me.table.getValueAt(row, TYPE_COLUMN).toString();
1860 button.setOpaque(true);
1861 button.setBackground(me.getBackground());
1862 button.setText(currentFilter.toString());
1863 button.setToolTipText(currentFilter.toString());
1864 button.setIcon(null);
1870 class FeatureIcon implements Icon
1872 FeatureColourI gcol;
1876 boolean midspace = false;
1878 int width = 50, height = 20;
1880 int s1, e1; // start and end of midpoint band for thresholded symbol
1882 Color mpcolour = Color.white;
1884 FeatureIcon(FeatureColourI gfc, Color bg, int w, int h, boolean mspace)
1904 public int getIconWidth()
1910 public int getIconHeight()
1916 public void paintIcon(Component c, Graphics g, int x, int y)
1919 if (gcol.isColourByLabel())
1922 g.fillRect(0, 0, width, height);
1923 // need an icon here.
1924 g.setColor(gcol.getMaxColour());
1926 g.setFont(new Font("Verdana", Font.PLAIN, 9));
1928 // g.setFont(g.getFont().deriveFont(
1929 // AffineTransform.getScaleInstance(
1930 // width/g.getFontMetrics().stringWidth("Label"),
1931 // height/g.getFontMetrics().getHeight())));
1933 g.drawString(MessageManager.getString("label.label"), 0, 0);
1938 Color minCol = gcol.getMinColour();
1940 g.fillRect(0, 0, s1, height);
1943 g.setColor(Color.white);
1944 g.fillRect(s1, 0, e1 - s1, height);
1946 g.setColor(gcol.getMaxColour());
1947 g.fillRect(0, e1, width - e1, height);