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.TableCellEditor;
99 import javax.swing.table.TableCellRenderer;
100 import javax.swing.table.TableColumn;
102 public class FeatureSettings extends JPanel
103 implements FeatureSettingsControllerI
105 private static final String SEQUENCE_FEATURE_COLOURS = MessageManager
106 .getString("label.sequence_feature_colours");
109 * column indices of fields in Feature Settings table
111 static final int TYPE_COLUMN = 0;
113 static final int COLOUR_COLUMN = 1;
115 static final int FILTER_COLUMN = 2;
117 static final int SHOW_COLUMN = 3;
119 private static final int COLUMN_COUNT = 4;
121 private static final int MIN_WIDTH = 400;
123 private static final int MIN_HEIGHT = 400;
125 final FeatureRenderer fr;
127 public final AlignFrame af;
130 * 'original' fields hold settings to restore on Cancel
132 Object[][] originalData;
134 private float originalTransparency;
136 private Map<String, FeatureMatcherSetI> originalFilters;
138 final JInternalFrame frame;
140 JScrollPane scrollPane = new JScrollPane();
146 JSlider transparency = new JSlider();
149 * when true, constructor is still executing - so ignore UI events
151 protected volatile boolean inConstruction = true;
153 int selectedRow = -1;
155 JButton fetchDAS = new JButton();
157 JButton saveDAS = new JButton();
159 JButton cancelDAS = new JButton();
161 boolean resettingTable = false;
164 * true when Feature Settings are updating from feature renderer
166 private boolean handlingUpdate = false;
169 * holds {featureCount, totalExtent} for each feature type
171 Map<String, float[]> typeWidth = null;
178 public FeatureSettings(AlignFrame alignFrame)
180 this.af = alignFrame;
181 fr = af.getFeatureRenderer();
183 // save transparency for restore on Cancel
184 originalTransparency = fr.getTransparency();
185 int originalTransparencyAsPercent = (int) (originalTransparency * 100);
186 transparency.setMaximum(100 - originalTransparencyAsPercent);
188 originalFilters = new HashMap<>(fr.getFeatureFilters()); // shallow copy
193 } catch (Exception ex)
195 ex.printStackTrace();
201 public String getToolTipText(MouseEvent e)
204 int column = table.columnAtPoint(e.getPoint());
208 tip = JvSwingUtils.wrapTooltip(true, MessageManager
209 .getString("label.feature_settings_click_drag"));
212 int row = table.rowAtPoint(e.getPoint());
213 FeatureMatcherSet o = (FeatureMatcherSet) table.getValueAt(row,
216 ? MessageManager.getString("label.filters_tooltip")
225 table.getTableHeader().setFont(new Font("Verdana", Font.PLAIN, 12));
226 table.setFont(new Font("Verdana", Font.PLAIN, 12));
228 // table.setDefaultRenderer(Color.class, new ColorRenderer());
229 // table.setDefaultEditor(Color.class, new ColorEditor(this));
231 table.setDefaultEditor(FeatureColour.class, new ColorEditor(this));
232 table.setDefaultRenderer(FeatureColour.class, new ColorRenderer());
234 table.setDefaultEditor(FeatureMatcherSet.class, new FilterEditor(this));
235 table.setDefaultRenderer(FeatureMatcherSet.class, new FilterRenderer());
237 TableColumn colourColumn = new TableColumn(COLOUR_COLUMN, 75,
238 new ColorRenderer(), new ColorEditor(this));
239 table.addColumn(colourColumn);
241 TableColumn filterColumn = new TableColumn(FILTER_COLUMN, 75,
242 new FilterRenderer(), new FilterEditor(this));
243 table.addColumn(filterColumn);
245 table.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
247 table.addMouseListener(new MouseAdapter()
250 public void mousePressed(MouseEvent evt)
252 selectedRow = table.rowAtPoint(evt.getPoint());
253 String type = (String) table.getValueAt(selectedRow, TYPE_COLUMN);
254 if (evt.isPopupTrigger())
256 Object colour = table.getValueAt(selectedRow, COLOUR_COLUMN);
257 popupSort(selectedRow, type, colour, fr.getMinMax(), evt.getX(),
260 else if (evt.getClickCount() == 2)
262 boolean invertSelection = evt.isAltDown();
263 boolean toggleSelection = Platform.isControlDown(evt);
264 boolean extendSelection = evt.isShiftDown();
265 fr.ap.alignFrame.avc.markColumnsContainingFeatures(
266 invertSelection, extendSelection, toggleSelection, type);
270 // isPopupTrigger fires on mouseReleased on Windows
272 public void mouseReleased(MouseEvent evt)
274 selectedRow = table.rowAtPoint(evt.getPoint());
275 if (evt.isPopupTrigger())
277 String type = (String) table.getValueAt(selectedRow, TYPE_COLUMN);
278 Object colour = table.getValueAt(selectedRow, COLOUR_COLUMN);
279 popupSort(selectedRow, type, colour, fr.getMinMax(), evt.getX(),
285 table.addMouseMotionListener(new MouseMotionAdapter()
288 public void mouseDragged(MouseEvent evt)
290 int newRow = table.rowAtPoint(evt.getPoint());
291 if (newRow != selectedRow && selectedRow != -1 && newRow != -1)
294 * reposition 'selectedRow' to 'newRow' (the dragged to location)
295 * this could be more than one row away for a very fast drag action
296 * so just swap it with adjacent rows until we get it there
298 Object[][] data = ((FeatureTableModel) table.getModel())
300 int direction = newRow < selectedRow ? -1 : 1;
301 for (int i = selectedRow; i != newRow; i += direction)
303 Object[] temp = data[i];
304 data[i] = data[i + direction];
305 data[i + direction] = temp;
307 updateFeatureRenderer(data);
309 selectedRow = newRow;
313 // table.setToolTipText(JvSwingUtils.wrapTooltip(true,
314 // MessageManager.getString("label.feature_settings_click_drag")));
315 scrollPane.setViewportView(table);
317 if (af.getViewport().isShowSequenceFeatures() || !fr.hasRenderOrder())
319 fr.findAllFeatures(true); // display everything!
322 discoverAllFeatureData();
323 final PropertyChangeListener change;
324 final FeatureSettings fs = this;
325 fr.addPropertyChangeListener(change = new PropertyChangeListener()
328 public void propertyChange(PropertyChangeEvent evt)
330 if (!fs.resettingTable && !fs.handlingUpdate)
332 fs.handlingUpdate = true;
334 // new groups may be added with new sequence feature types only
335 fs.handlingUpdate = false;
341 frame = new JInternalFrame();
342 frame.setContentPane(this);
343 if (Platform.isAMac())
345 Desktop.addInternalFrame(frame,
346 MessageManager.getString("label.sequence_feature_settings"),
351 Desktop.addInternalFrame(frame,
352 MessageManager.getString("label.sequence_feature_settings"),
355 frame.setMinimumSize(new Dimension(MIN_WIDTH, MIN_HEIGHT));
357 frame.addInternalFrameListener(
358 new javax.swing.event.InternalFrameAdapter()
361 public void internalFrameClosed(
362 javax.swing.event.InternalFrameEvent evt)
364 fr.removePropertyChangeListener(change);
367 frame.setLayer(JLayeredPane.PALETTE_LAYER);
368 inConstruction = false;
371 protected void popupSort(final int rowSelected, final String type,
372 final Object typeCol, final Map<String, float[][]> minmax, int x,
375 final FeatureColourI featureColour = (FeatureColourI) typeCol;
377 JPopupMenu men = new JPopupMenu(MessageManager
378 .formatMessage("label.settings_for_param", new String[]
380 JMenuItem scr = new JMenuItem(
381 MessageManager.getString("label.sort_by_score"));
383 final FeatureSettings me = this;
384 scr.addActionListener(new ActionListener()
388 public void actionPerformed(ActionEvent e)
391 .sortAlignmentByFeatureScore(Arrays.asList(new String[]
396 JMenuItem dens = new JMenuItem(
397 MessageManager.getString("label.sort_by_density"));
398 dens.addActionListener(new ActionListener()
402 public void actionPerformed(ActionEvent e)
405 .sortAlignmentByFeatureDensity(Arrays.asList(new String[]
413 * variable colour options include colour by label, by score,
414 * by selected attribute text, or attribute value
416 final JCheckBoxMenuItem mxcol = new JCheckBoxMenuItem(
417 MessageManager.getString("label.variable_colour"));
418 mxcol.setSelected(!featureColour.isSimpleColour());
420 mxcol.addActionListener(new ActionListener()
422 JColorChooser colorChooser;
425 public void actionPerformed(ActionEvent e)
427 if (e.getSource() == mxcol)
429 if (featureColour.isSimpleColour())
431 FeatureTypeSettings fc = new FeatureTypeSettings(me.fr, type);
432 fc.addActionListener(this);
436 // bring up simple color chooser
437 colorChooser = new JColorChooser();
438 String title = MessageManager
439 .getString("label.select_colour");
440 JDialog dialog = JColorChooser.createDialog(me,
441 title, true, // modal
442 colorChooser, this, // OK button handler
443 null); // no CANCEL button handler
444 colorChooser.setColor(featureColour.getMaxColour());
445 dialog.setVisible(true);
450 if (e.getSource() instanceof FeatureTypeSettings)
453 * update after OK in feature colour dialog; the updated
454 * colour will have already been set in the FeatureRenderer
456 FeatureColourI fci = fr.getFeatureColours().get(type);
457 table.setValueAt(fci, rowSelected, 1);
462 // probably the color chooser!
463 table.setValueAt(new FeatureColour(colorChooser.getColor()),
466 me.updateFeatureRenderer(
467 ((FeatureTableModel) table.getModel()).getData(),
475 JMenuItem selCols = new JMenuItem(
476 MessageManager.getString("label.select_columns_containing"));
477 selCols.addActionListener(new ActionListener()
480 public void actionPerformed(ActionEvent arg0)
482 fr.ap.alignFrame.avc.markColumnsContainingFeatures(false, false,
486 JMenuItem clearCols = new JMenuItem(MessageManager
487 .getString("label.select_columns_not_containing"));
488 clearCols.addActionListener(new ActionListener()
491 public void actionPerformed(ActionEvent arg0)
493 fr.ap.alignFrame.avc.markColumnsContainingFeatures(true, false,
497 JMenuItem hideCols = new JMenuItem(
498 MessageManager.getString("label.hide_columns_containing"));
499 hideCols.addActionListener(new ActionListener()
502 public void actionPerformed(ActionEvent arg0)
504 fr.ap.alignFrame.hideFeatureColumns(type, true);
507 JMenuItem hideOtherCols = new JMenuItem(
508 MessageManager.getString("label.hide_columns_not_containing"));
509 hideOtherCols.addActionListener(new ActionListener()
512 public void actionPerformed(ActionEvent arg0)
514 fr.ap.alignFrame.hideFeatureColumns(type, false);
520 men.add(hideOtherCols);
521 men.show(table, x, y);
525 synchronized public void discoverAllFeatureData()
527 Set<String> allGroups = new HashSet<>();
528 AlignmentI alignment = af.getViewport().getAlignment();
530 for (int i = 0; i < alignment.getHeight(); i++)
532 SequenceI seq = alignment.getSequenceAt(i);
533 for (String group : seq.getFeatures().getFeatureGroups(true))
535 if (group != null && !allGroups.contains(group))
537 allGroups.add(group);
538 checkGroupState(group);
549 * Synchronise gui group list and check visibility of group
552 * @return true if group is visible
554 private boolean checkGroupState(String group)
556 boolean visible = fr.checkGroupVisibility(group, true);
558 for (int g = 0; g < groupPanel.getComponentCount(); g++)
560 if (((JCheckBox) groupPanel.getComponent(g)).getText().equals(group))
562 ((JCheckBox) groupPanel.getComponent(g)).setSelected(visible);
567 final String grp = group;
568 final JCheckBox check = new JCheckBox(group, visible);
569 check.setFont(new Font("Serif", Font.BOLD, 12));
570 check.setToolTipText(group);
571 check.addItemListener(new ItemListener()
574 public void itemStateChanged(ItemEvent evt)
576 fr.setGroupVisibility(check.getText(), check.isSelected());
577 resetTable(new String[] { grp });
578 af.alignPanel.paintAlignment(true, true);
581 groupPanel.add(check);
585 synchronized void resetTable(String[] groupChanged)
591 resettingTable = true;
592 typeWidth = new Hashtable<>();
593 // TODO: change avWidth calculation to 'per-sequence' average and use long
596 Set<String> displayableTypes = new HashSet<>();
597 Set<String> foundGroups = new HashSet<>();
600 * determine which feature types may be visible depending on
601 * which groups are selected, and recompute average width data
603 for (int i = 0; i < af.getViewport().getAlignment().getHeight(); i++)
606 SequenceI seq = af.getViewport().getAlignment().getSequenceAt(i);
609 * get the sequence's groups for positional features
610 * and keep track of which groups are visible
612 Set<String> groups = seq.getFeatures().getFeatureGroups(true);
613 Set<String> visibleGroups = new HashSet<>();
614 for (String group : groups)
616 if (group == null || checkGroupState(group))
618 visibleGroups.add(group);
621 foundGroups.addAll(groups);
624 * get distinct feature types for visible groups
625 * record distinct visible types, and their count and total length
627 Set<String> types = seq.getFeatures().getFeatureTypesForGroups(true,
628 visibleGroups.toArray(new String[visibleGroups.size()]));
629 for (String type : types)
631 displayableTypes.add(type);
632 float[] avWidth = typeWidth.get(type);
635 avWidth = new float[2];
636 typeWidth.put(type, avWidth);
638 // todo this could include features with a non-visible group
639 // - do we greatly care?
640 // todo should we include non-displayable features here, and only
641 // update when features are added?
642 avWidth[0] += seq.getFeatures().getFeatureCount(true, type);
643 avWidth[1] += seq.getFeatures().getTotalFeatureLength(type);
647 Object[][] data = new Object[displayableTypes.size()][COLUMN_COUNT];
650 if (fr.hasRenderOrder())
654 fr.findAllFeatures(groupChanged != null); // prod to update
655 // colourschemes. but don't
657 // First add the checks in the previous render order,
658 // in case the window has been closed and reopened
660 List<String> frl = fr.getRenderOrder();
661 for (int ro = frl.size() - 1; ro > -1; ro--)
663 String type = frl.get(ro);
665 if (!displayableTypes.contains(type))
670 data[dataIndex][TYPE_COLUMN] = type;
671 data[dataIndex][COLOUR_COLUMN] = fr.getFeatureStyle(type);
672 FeatureMatcherSetI featureFilter = fr.getFeatureFilter(type);
673 data[dataIndex][FILTER_COLUMN] = featureFilter == null
674 ? new FeatureMatcherSet()
676 data[dataIndex][SHOW_COLUMN] = new Boolean(
677 af.getViewport().getFeaturesDisplayed().isVisible(type));
679 displayableTypes.remove(type);
684 * process any extra features belonging only to
685 * a group which was just selected
687 while (!displayableTypes.isEmpty())
689 String type = displayableTypes.iterator().next();
690 data[dataIndex][TYPE_COLUMN] = type;
692 data[dataIndex][COLOUR_COLUMN] = fr.getFeatureStyle(type);
693 if (data[dataIndex][COLOUR_COLUMN] == null)
695 // "Colour has been updated in another view!!"
696 fr.clearRenderOrder();
699 FeatureMatcherSetI featureFilter = fr.getFeatureFilter(type);
700 data[dataIndex][FILTER_COLUMN] = featureFilter == null
701 ? new FeatureMatcherSet()
703 data[dataIndex][SHOW_COLUMN] = new Boolean(true);
705 displayableTypes.remove(type);
708 if (originalData == null)
710 originalData = new Object[data.length][COLUMN_COUNT];
711 for (int i = 0; i < data.length; i++)
713 System.arraycopy(data[i], 0, originalData[i], 0, COLUMN_COUNT);
718 updateOriginalData(data);
721 table.setModel(new FeatureTableModel(data));
722 table.getColumnModel().getColumn(0).setPreferredWidth(200);
724 groupPanel.setLayout(
725 new GridLayout(fr.getFeatureGroupsSize() / 4 + 1, 4));
726 pruneGroups(foundGroups);
727 groupPanel.validate();
729 updateFeatureRenderer(data, groupChanged != null);
730 resettingTable = false;
734 * Updates 'originalData' (used for restore on Cancel) if we detect that changes
735 * have been made outwith this dialog
737 * <li>a new feature type added (and made visible)</li>
738 * <li>a feature colour changed (in the Amend Features dialog)</li>
743 protected void updateOriginalData(Object[][] foundData)
745 // todo LinkedHashMap instead of Object[][] would be nice
747 Object[][] currentData = ((FeatureTableModel) table.getModel())
749 for (Object[] row : foundData)
751 String type = (String) row[TYPE_COLUMN];
752 boolean found = false;
753 for (Object[] current : currentData)
755 if (type.equals(current[TYPE_COLUMN]))
759 * currently dependent on object equality here;
760 * really need an equals method on FeatureColour
762 if (!row[COLOUR_COLUMN].equals(current[COLOUR_COLUMN]))
765 * feature colour has changed externally - update originalData
767 for (Object[] original : originalData)
769 if (type.equals(original[TYPE_COLUMN]))
771 original[COLOUR_COLUMN] = row[COLOUR_COLUMN];
782 * new feature detected - add to original data (on top)
784 Object[][] newData = new Object[originalData.length
786 for (int i = 0; i < originalData.length; i++)
788 System.arraycopy(originalData[i], 0, newData[i + 1], 0,
792 originalData = newData;
798 * Remove from the groups panel any checkboxes for groups that are not in the
799 * foundGroups set. This enables removing a group from the display when the last
800 * feature in that group is deleted.
804 protected void pruneGroups(Set<String> foundGroups)
806 for (int g = 0; g < groupPanel.getComponentCount(); g++)
808 JCheckBox checkbox = (JCheckBox) groupPanel.getComponent(g);
809 if (!foundGroups.contains(checkbox.getText()))
811 groupPanel.remove(checkbox);
817 * reorder data based on the featureRenderers global priority list.
821 private void ensureOrder(Object[][] data)
823 boolean sort = false;
824 float[] order = new float[data.length];
825 for (int i = 0; i < order.length; i++)
827 order[i] = fr.getOrder(data[i][0].toString());
830 order[i] = fr.setOrder(data[i][0].toString(), i / order.length);
834 sort = sort || order[i - 1] > order[i];
839 jalview.util.QuickSort.sort(order, data);
844 * Offers a file chooser dialog, and then loads the feature colours and
845 * filters from file in XML format and unmarshals to Jalview feature settings
849 JalviewFileChooser chooser = new JalviewFileChooser("fc",
850 SEQUENCE_FEATURE_COLOURS);
851 chooser.setFileView(new JalviewFileView());
852 chooser.setDialogTitle(
853 MessageManager.getString("label.load_feature_colours"));
854 chooser.setToolTipText(MessageManager.getString("action.load"));
856 int value = chooser.showOpenDialog(this);
858 if (value == JalviewFileChooser.APPROVE_OPTION)
860 File file = chooser.getSelectedFile();
866 * Loads feature colours and filters from XML stored in the given file
874 InputStreamReader in = new InputStreamReader(
875 new FileInputStream(file), "UTF-8");
877 JalviewUserColours jucs = JalviewUserColours.unmarshal(in);
880 * load feature colours
882 for (int i = jucs.getColourCount() - 1; i >= 0; i--)
884 jalview.schemabinding.version2.Colour newcol = jucs.getColour(i);
885 FeatureColourI colour = Jalview2XML.unmarshalColour(newcol);
886 fr.setColour(newcol.getName(), colour);
887 fr.setOrder(newcol.getName(), i / (float) jucs.getColourCount());
891 * load feature filters; loaded filters will replace any that are
892 * currently defined, other defined filters are left unchanged
894 for (int i = 0; i < jucs.getFilterCount(); i++)
896 jalview.schemabinding.version2.Filter filterModel = jucs
898 String featureType = filterModel.getFeatureType();
899 FeatureMatcherSetI filter = Jalview2XML.unmarshalFilter(featureType,
900 filterModel.getMatcherSet());
901 if (!filter.isEmpty())
903 fr.setFeatureFilter(featureType, filter);
908 * update feature settings table
913 Object[][] data = ((FeatureTableModel) table.getModel())
916 updateFeatureRenderer(data, false);
919 } catch (Exception ex)
921 System.out.println("Error loading User Colour File\n" + ex);
926 * Offers a file chooser dialog, and then saves the current feature colours
927 * and any filters to the selected file in XML format
931 JalviewFileChooser chooser = new JalviewFileChooser("fc",
932 SEQUENCE_FEATURE_COLOURS);
933 chooser.setFileView(new JalviewFileView());
934 chooser.setDialogTitle(
935 MessageManager.getString("label.save_feature_colours"));
936 chooser.setToolTipText(MessageManager.getString("action.save"));
938 int value = chooser.showSaveDialog(this);
940 if (value == JalviewFileChooser.APPROVE_OPTION)
942 save(chooser.getSelectedFile());
947 * Saves feature colours and filters to the given file
953 JalviewUserColours ucs = new JalviewUserColours();
954 ucs.setSchemeName("Sequence Features");
957 PrintWriter out = new PrintWriter(new OutputStreamWriter(
958 new FileOutputStream(file), "UTF-8"));
961 * sort feature types by colour order, from 0 (highest)
964 Set<String> fr_colours = fr.getAllFeatureColours();
965 String[] sortedTypes = fr_colours
966 .toArray(new String[fr_colours.size()]);
967 Arrays.sort(sortedTypes, new Comparator<String>()
970 public int compare(String type1, String type2)
972 return Float.compare(fr.getOrder(type1), fr.getOrder(type2));
977 * save feature colours
979 for (String featureType : sortedTypes)
981 FeatureColourI fcol = fr.getFeatureStyle(featureType);
982 jalview.schemabinding.version2.Colour col = Jalview2XML.marshalColour(
988 * save any feature filters
990 for (String featureType : sortedTypes)
992 FeatureMatcherSetI filter = fr.getFeatureFilter(featureType);
993 if (filter != null && !filter.isEmpty())
995 Iterator<FeatureMatcherI> iterator = filter.getMatchers().iterator();
996 FeatureMatcherI firstMatcher = iterator.next();
997 MatcherSet ms = Jalview2XML.marshalFilter(firstMatcher, iterator,
999 Filter filterModel = new Filter();
1000 filterModel.setFeatureType(featureType);
1001 filterModel.setMatcherSet(ms);
1002 ucs.addFilter(filterModel);
1008 } catch (Exception ex)
1010 ex.printStackTrace();
1014 public void invertSelection()
1016 Object[][] data = ((FeatureTableModel) table.getModel()).getData();
1017 for (int i = 0; i < data.length; i++)
1019 data[i][SHOW_COLUMN] = !(Boolean) data[i][SHOW_COLUMN];
1021 updateFeatureRenderer(data, true);
1025 public void orderByAvWidth()
1027 if (table == null || table.getModel() == null)
1031 Object[][] data = ((FeatureTableModel) table.getModel()).getData();
1032 float[] width = new float[data.length];
1036 for (int i = 0; i < data.length; i++)
1038 awidth = typeWidth.get(data[i][TYPE_COLUMN]);
1041 width[i] = awidth[1] / awidth[0];// *awidth[0]*awidth[2]; - better
1042 // weight - but have to make per
1043 // sequence, too (awidth[2])
1044 // if (width[i]==1) // hack to distinguish single width sequences.
1055 boolean sort = false;
1056 for (int i = 0; i < width.length; i++)
1058 // awidth = (float[]) typeWidth.get(data[i][0]);
1061 width[i] = fr.getOrder(data[i][TYPE_COLUMN].toString());
1064 width[i] = fr.setOrder(data[i][TYPE_COLUMN].toString(),
1070 width[i] /= max; // normalize
1071 fr.setOrder(data[i][TYPE_COLUMN].toString(), width[i]); // store for later
1075 sort = sort || width[i - 1] > width[i];
1080 jalview.util.QuickSort.sort(width, data);
1081 // update global priority order
1084 updateFeatureRenderer(data, false);
1092 frame.setClosed(true);
1093 } catch (Exception exe)
1099 public void updateFeatureRenderer(Object[][] data)
1101 updateFeatureRenderer(data, true);
1105 * Update the priority order of features; only repaint if this changed the order
1106 * of visible features
1111 private void updateFeatureRenderer(Object[][] data, boolean visibleNew)
1113 FeatureSettingsBean[] rowData = getTableAsBeans(data);
1115 if (fr.setFeaturePriority(rowData, visibleNew))
1117 af.alignPanel.paintAlignment(true, true);
1122 * Converts table data into an array of data beans
1124 private FeatureSettingsBean[] getTableAsBeans(Object[][] data)
1126 FeatureSettingsBean[] rowData = new FeatureSettingsBean[data.length];
1127 for (int i = 0; i < data.length; i++)
1129 String type = (String) data[i][TYPE_COLUMN];
1130 FeatureColourI colour = (FeatureColourI) data[i][COLOUR_COLUMN];
1131 FeatureMatcherSetI theFilter = (FeatureMatcherSetI) data[i][FILTER_COLUMN];
1132 Boolean isShown = (Boolean) data[i][SHOW_COLUMN];
1133 rowData[i] = new FeatureSettingsBean(type, colour, theFilter,
1139 private void jbInit() throws Exception
1141 this.setLayout(new BorderLayout());
1143 JPanel settingsPane = new JPanel();
1144 settingsPane.setLayout(new BorderLayout());
1146 JPanel bigPanel = new JPanel();
1147 bigPanel.setLayout(new BorderLayout());
1149 groupPanel = new JPanel();
1150 bigPanel.add(groupPanel, BorderLayout.NORTH);
1152 JButton invert = new JButton(
1153 MessageManager.getString("label.invert_selection"));
1154 invert.setFont(JvSwingUtils.getLabelFont());
1155 invert.addActionListener(new ActionListener()
1158 public void actionPerformed(ActionEvent e)
1164 JButton optimizeOrder = new JButton(
1165 MessageManager.getString("label.optimise_order"));
1166 optimizeOrder.setFont(JvSwingUtils.getLabelFont());
1167 optimizeOrder.addActionListener(new ActionListener()
1170 public void actionPerformed(ActionEvent e)
1176 JButton sortByScore = new JButton(
1177 MessageManager.getString("label.seq_sort_by_score"));
1178 sortByScore.setFont(JvSwingUtils.getLabelFont());
1179 sortByScore.addActionListener(new ActionListener()
1182 public void actionPerformed(ActionEvent e)
1184 af.avc.sortAlignmentByFeatureScore(null);
1187 JButton sortByDens = new JButton(
1188 MessageManager.getString("label.sequence_sort_by_density"));
1189 sortByDens.setFont(JvSwingUtils.getLabelFont());
1190 sortByDens.addActionListener(new ActionListener()
1193 public void actionPerformed(ActionEvent e)
1195 af.avc.sortAlignmentByFeatureDensity(null);
1199 JButton help = new JButton(MessageManager.getString("action.help"));
1200 help.setFont(JvSwingUtils.getLabelFont());
1201 help.addActionListener(new ActionListener()
1204 public void actionPerformed(ActionEvent e)
1208 Help.showHelpWindow(HelpId.SequenceFeatureSettings);
1209 } catch (HelpSetException e1)
1211 e1.printStackTrace();
1215 help.setFont(JvSwingUtils.getLabelFont());
1216 help.setText(MessageManager.getString("action.help"));
1217 help.addActionListener(new ActionListener()
1220 public void actionPerformed(ActionEvent e)
1224 Help.showHelpWindow(HelpId.SequenceFeatureSettings);
1225 } catch (HelpSetException e1)
1227 e1.printStackTrace();
1232 JButton cancel = new JButton(MessageManager.getString("action.cancel"));
1233 cancel.setFont(JvSwingUtils.getLabelFont());
1234 cancel.addActionListener(new ActionListener()
1237 public void actionPerformed(ActionEvent e)
1239 fr.setTransparency(originalTransparency);
1240 fr.setFeatureFilters(originalFilters);
1241 updateFeatureRenderer(originalData);
1246 JButton ok = new JButton(MessageManager.getString("action.ok"));
1247 ok.setFont(JvSwingUtils.getLabelFont());
1248 ok.addActionListener(new ActionListener()
1251 public void actionPerformed(ActionEvent e)
1257 JButton loadColours = new JButton(
1258 MessageManager.getString("label.load_colours"));
1259 loadColours.setFont(JvSwingUtils.getLabelFont());
1260 loadColours.setToolTipText(
1261 MessageManager.getString("label.load_colours_tooltip"));
1262 loadColours.addActionListener(new ActionListener()
1265 public void actionPerformed(ActionEvent e)
1271 JButton saveColours = new JButton(
1272 MessageManager.getString("label.save_colours"));
1273 saveColours.setFont(JvSwingUtils.getLabelFont());
1274 saveColours.setToolTipText(
1275 MessageManager.getString("label.save_colours_tooltip"));
1276 saveColours.addActionListener(new ActionListener()
1279 public void actionPerformed(ActionEvent e)
1284 transparency.addChangeListener(new ChangeListener()
1287 public void stateChanged(ChangeEvent evt)
1289 if (!inConstruction)
1291 fr.setTransparency((100 - transparency.getValue()) / 100f);
1292 af.alignPanel.paintAlignment(true, true);
1297 transparency.setMaximum(70);
1298 transparency.setToolTipText(
1299 MessageManager.getString("label.transparency_tip"));
1301 JPanel transPanel = new JPanel(new GridLayout(1, 2));
1302 bigPanel.add(transPanel, BorderLayout.SOUTH);
1304 JPanel transbuttons = new JPanel(new GridLayout(5, 1));
1305 transbuttons.add(optimizeOrder);
1306 transbuttons.add(invert);
1307 transbuttons.add(sortByScore);
1308 transbuttons.add(sortByDens);
1309 transbuttons.add(help);
1310 transPanel.add(transparency);
1311 transPanel.add(transbuttons);
1313 JPanel buttonPanel = new JPanel();
1314 buttonPanel.add(ok);
1315 buttonPanel.add(cancel);
1316 buttonPanel.add(loadColours);
1317 buttonPanel.add(saveColours);
1318 bigPanel.add(scrollPane, BorderLayout.CENTER);
1319 settingsPane.add(bigPanel, BorderLayout.CENTER);
1320 settingsPane.add(buttonPanel, BorderLayout.SOUTH);
1321 this.add(settingsPane);
1324 // ///////////////////////////////////////////////////////////////////////
1325 // http://java.sun.com/docs/books/tutorial/uiswing/components/table.html
1326 // ///////////////////////////////////////////////////////////////////////
1327 class FeatureTableModel extends AbstractTableModel
1329 private String[] columnNames = {
1330 MessageManager.getString("label.feature_type"),
1331 MessageManager.getString("action.colour"),
1332 MessageManager.getString("label.filter"),
1333 MessageManager.getString("label.show") };
1335 private Object[][] data;
1337 FeatureTableModel(Object[][] data)
1342 public Object[][] getData()
1347 public void setData(Object[][] data)
1353 public int getColumnCount()
1355 return columnNames.length;
1358 public Object[] getRow(int row)
1364 public int getRowCount()
1370 public String getColumnName(int col)
1372 return columnNames[col];
1376 public Object getValueAt(int row, int col)
1378 return data[row][col];
1382 * Answers the class of the object in column c of the first row of the table
1385 public Class<?> getColumnClass(int c)
1387 Object v = getValueAt(0, c);
1388 return v == null ? null : v.getClass();
1392 public boolean isCellEditable(int row, int col)
1394 return col == 0 ? false : true;
1398 public void setValueAt(Object value, int row, int col)
1400 data[row][col] = value;
1401 fireTableCellUpdated(row, col);
1402 updateFeatureRenderer(data);
1407 class ColorRenderer extends JLabel implements TableCellRenderer
1409 javax.swing.border.Border unselectedBorder = null;
1411 javax.swing.border.Border selectedBorder = null;
1413 final String baseTT = "Click to edit, right/apple click for menu.";
1415 public ColorRenderer()
1417 setOpaque(true); // MUST do this for background to show up.
1418 setHorizontalTextPosition(SwingConstants.CENTER);
1419 setVerticalTextPosition(SwingConstants.CENTER);
1423 public Component getTableCellRendererComponent(JTable tbl, Object color,
1424 boolean isSelected, boolean hasFocus, int row, int column)
1426 FeatureColourI cellColour = (FeatureColourI) color;
1428 setToolTipText(baseTT);
1429 setBackground(tbl.getBackground());
1430 if (!cellColour.isSimpleColour())
1432 Rectangle cr = tbl.getCellRect(row, column, false);
1433 FeatureSettings.renderGraduatedColor(this, cellColour,
1434 (int) cr.getWidth(), (int) cr.getHeight());
1440 setBackground(cellColour.getColour());
1444 if (selectedBorder == null)
1446 selectedBorder = BorderFactory.createMatteBorder(2, 5, 2, 5,
1447 tbl.getSelectionBackground());
1449 setBorder(selectedBorder);
1453 if (unselectedBorder == null)
1455 unselectedBorder = BorderFactory.createMatteBorder(2, 5, 2, 5,
1456 tbl.getBackground());
1458 setBorder(unselectedBorder);
1465 class FilterRenderer extends JLabel implements TableCellRenderer
1467 javax.swing.border.Border unselectedBorder = null;
1469 javax.swing.border.Border selectedBorder = null;
1471 public FilterRenderer()
1473 setOpaque(true); // MUST do this for background to show up.
1474 setHorizontalTextPosition(SwingConstants.CENTER);
1475 setVerticalTextPosition(SwingConstants.CENTER);
1479 public Component getTableCellRendererComponent(JTable tbl,
1480 Object filter, boolean isSelected, boolean hasFocus, int row,
1483 FeatureMatcherSetI theFilter = (FeatureMatcherSetI) filter;
1485 String asText = theFilter.toString();
1486 setBackground(tbl.getBackground());
1487 this.setText(asText);
1492 if (selectedBorder == null)
1494 selectedBorder = BorderFactory.createMatteBorder(2, 5, 2, 5,
1495 tbl.getSelectionBackground());
1497 setBorder(selectedBorder);
1501 if (unselectedBorder == null)
1503 unselectedBorder = BorderFactory.createMatteBorder(2, 5, 2, 5,
1504 tbl.getBackground());
1506 setBorder(unselectedBorder);
1514 * update comp using rendering settings from gcol
1519 public static void renderGraduatedColor(JLabel comp, FeatureColourI gcol)
1521 int w = comp.getWidth(), h = comp.getHeight();
1524 w = (int) comp.getPreferredSize().getWidth();
1525 h = (int) comp.getPreferredSize().getHeight();
1532 renderGraduatedColor(comp, gcol, w, h);
1535 public static void renderGraduatedColor(JLabel comp, FeatureColourI gcol,
1538 boolean thr = false;
1539 StringBuilder tt = new StringBuilder();
1540 StringBuilder tx = new StringBuilder();
1542 if (gcol.isColourByAttribute())
1544 tx.append(String.join(":", gcol.getAttributeName()));
1546 else if (!gcol.isColourByLabel())
1548 tx.append(MessageManager.getString("label.score"));
1551 if (gcol.isAboveThreshold())
1555 tt.append("Thresholded (Above ").append(gcol.getThreshold())
1558 if (gcol.isBelowThreshold())
1562 tt.append("Thresholded (Below ").append(gcol.getThreshold())
1565 if (gcol.isColourByLabel())
1567 tt.append("Coloured by label text. ").append(tt);
1572 if (!gcol.isColourByAttribute())
1580 Color newColor = gcol.getMaxColour();
1581 comp.setBackground(newColor);
1582 // System.err.println("Width is " + w / 2);
1583 Icon ficon = new FeatureIcon(gcol, comp.getBackground(), w, h, thr);
1584 comp.setIcon(ficon);
1585 // tt+="RGB value: Max (" + newColor.getRed() + ", "
1586 // + newColor.getGreen() + ", " + newColor.getBlue()
1587 // + ")\nMin (" + minCol.getRed() + ", " + minCol.getGreen()
1588 // + ", " + minCol.getBlue() + ")");
1590 comp.setHorizontalAlignment(SwingConstants.CENTER);
1591 comp.setText(tx.toString());
1592 if (tt.length() > 0)
1594 if (comp.getToolTipText() == null)
1596 comp.setToolTipText(tt.toString());
1600 comp.setToolTipText(
1601 tt.append(" ").append(comp.getToolTipText()).toString());
1606 class ColorEditor extends AbstractCellEditor
1607 implements TableCellEditor, ActionListener
1611 FeatureColourI currentColor;
1613 FeatureTypeSettings chooser;
1619 JColorChooser colorChooser;
1623 protected static final String EDIT = "edit";
1625 int rowSelected = 0;
1627 public ColorEditor(FeatureSettings me)
1630 // Set up the editor (from the table's point of view),
1631 // which is a button.
1632 // This button brings up the color chooser dialog,
1633 // which is the editor from the user's point of view.
1634 button = new JButton();
1635 button.setActionCommand(EDIT);
1636 button.addActionListener(this);
1637 button.setBorderPainted(false);
1638 // Set up the dialog that the button brings up.
1639 colorChooser = new JColorChooser();
1640 dialog = JColorChooser.createDialog(button,
1641 MessageManager.getString("label.select_colour"), true, // modal
1642 colorChooser, this, // OK button handler
1643 null); // no CANCEL button handler
1647 * Handles events from the editor button and from the dialog's OK button.
1650 public void actionPerformed(ActionEvent e)
1652 // todo test e.getSource() instead here
1653 if (EDIT.equals(e.getActionCommand()))
1655 // The user has clicked the cell, so
1656 // bring up the dialog.
1657 if (currentColor.isSimpleColour())
1659 // bring up simple color chooser
1660 button.setBackground(currentColor.getColour());
1661 colorChooser.setColor(currentColor.getColour());
1662 dialog.setVisible(true);
1666 // bring up graduated chooser.
1667 chooser = new FeatureTypeSettings(me.fr, type);
1672 chooser.setRequestFocusEnabled(true);
1673 chooser.requestFocus();
1675 chooser.addActionListener(this);
1676 // Make the renderer reappear.
1677 fireEditingStopped();
1682 if (currentColor.isSimpleColour())
1685 * read off colour picked in colour chooser after OK pressed
1687 currentColor = new FeatureColour(colorChooser.getColor());
1688 me.table.setValueAt(currentColor, rowSelected, COLOUR_COLUMN);
1693 * after OK in variable colour dialog, any changes to colour
1694 * (or filters!) are already set in FeatureRenderer, so just
1695 * update table data without triggering updateFeatureRenderer
1697 currentColor = fr.getFeatureColours().get(type);
1698 FeatureMatcherSetI currentFilter = me.fr.getFeatureFilter(type);
1699 if (currentFilter == null)
1701 currentFilter = new FeatureMatcherSet();
1703 Object[] data = ((FeatureTableModel) table.getModel())
1704 .getData()[rowSelected];
1705 data[COLOUR_COLUMN] = currentColor;
1706 data[FILTER_COLUMN] = currentFilter;
1708 fireEditingStopped();
1709 me.table.validate();
1713 // Implement the one CellEditor method that AbstractCellEditor doesn't.
1715 public Object getCellEditorValue()
1717 return currentColor;
1720 // Implement the one method defined by TableCellEditor.
1722 public Component getTableCellEditorComponent(JTable theTable, Object value,
1723 boolean isSelected, int row, int column)
1725 currentColor = (FeatureColourI) value;
1726 this.rowSelected = row;
1727 type = me.table.getValueAt(row, TYPE_COLUMN).toString();
1728 button.setOpaque(true);
1729 button.setBackground(me.getBackground());
1730 if (!currentColor.isSimpleColour())
1732 JLabel btn = new JLabel();
1733 btn.setSize(button.getSize());
1734 FeatureSettings.renderGraduatedColor(btn, currentColor);
1735 button.setBackground(btn.getBackground());
1736 button.setIcon(btn.getIcon());
1737 button.setText(btn.getText());
1742 button.setIcon(null);
1743 button.setBackground(currentColor.getColour());
1750 * The cell editor for the Filter column. It displays the text of any filters
1751 * for the feature type in that row (in full as a tooltip, possible abbreviated
1752 * as display text). On click in the cell, opens the Feature Display Settings
1753 * dialog at the Filters tab.
1755 class FilterEditor extends AbstractCellEditor
1756 implements TableCellEditor, ActionListener
1760 FeatureMatcherSetI currentFilter;
1768 protected static final String EDIT = "edit";
1770 int rowSelected = 0;
1772 public FilterEditor(FeatureSettings me)
1775 button = new JButton();
1776 button.setActionCommand(EDIT);
1777 button.addActionListener(this);
1778 button.setBorderPainted(false);
1782 * Handles events from the editor button
1785 public void actionPerformed(ActionEvent e)
1787 if (button == e.getSource())
1789 FeatureTypeSettings chooser = new FeatureTypeSettings(me.fr, type);
1790 chooser.addActionListener(this);
1791 chooser.setRequestFocusEnabled(true);
1792 chooser.requestFocus();
1793 if (lastLocation != null)
1795 // todo open at its last position on screen
1796 chooser.setBounds(lastLocation.x, lastLocation.y,
1797 chooser.getWidth(), chooser.getHeight());
1800 fireEditingStopped();
1802 else if (e.getSource() instanceof Component)
1806 * after OK in variable colour dialog, any changes to filter
1807 * (or colours!) are already set in FeatureRenderer, so just
1808 * update table data without triggering updateFeatureRenderer
1810 FeatureColourI currentColor = fr.getFeatureColours().get(type);
1811 currentFilter = me.fr.getFeatureFilter(type);
1812 if (currentFilter == null)
1814 currentFilter = new FeatureMatcherSet();
1816 Object[] data = ((FeatureTableModel) table.getModel())
1817 .getData()[rowSelected];
1818 data[COLOUR_COLUMN] = currentColor;
1819 data[FILTER_COLUMN] = currentFilter;
1820 fireEditingStopped();
1821 me.table.validate();
1826 public Object getCellEditorValue()
1828 return currentFilter;
1832 public Component getTableCellEditorComponent(JTable theTable, Object value,
1833 boolean isSelected, int row, int column)
1835 currentFilter = (FeatureMatcherSetI) value;
1836 this.rowSelected = row;
1837 type = me.table.getValueAt(row, TYPE_COLUMN).toString();
1838 button.setOpaque(true);
1839 button.setBackground(me.getBackground());
1840 button.setText(currentFilter.toString());
1841 button.setToolTipText(currentFilter.toString());
1842 button.setIcon(null);
1848 class FeatureIcon implements Icon
1850 FeatureColourI gcol;
1854 boolean midspace = false;
1856 int width = 50, height = 20;
1858 int s1, e1; // start and end of midpoint band for thresholded symbol
1860 Color mpcolour = Color.white;
1862 FeatureIcon(FeatureColourI gfc, Color bg, int w, int h, boolean mspace)
1882 public int getIconWidth()
1888 public int getIconHeight()
1894 public void paintIcon(Component c, Graphics g, int x, int y)
1897 if (gcol.isColourByLabel())
1900 g.fillRect(0, 0, width, height);
1901 // need an icon here.
1902 g.setColor(gcol.getMaxColour());
1904 g.setFont(new Font("Verdana", Font.PLAIN, 9));
1906 // g.setFont(g.getFont().deriveFont(
1907 // AffineTransform.getScaleInstance(
1908 // width/g.getFontMetrics().stringWidth("Label"),
1909 // height/g.getFontMetrics().getHeight())));
1911 g.drawString(MessageManager.getString("label.label"), 0, 0);
1916 Color minCol = gcol.getMinColour();
1918 g.fillRect(0, 0, s1, height);
1921 g.setColor(Color.white);
1922 g.fillRect(s1, 0, e1 - s1, height);
1924 g.setColor(gcol.getMaxColour());
1925 g.fillRect(0, e1, width - e1, height);