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 float originalTransparency;
136 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 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, COLOUR_COLUMN);
462 // probably the color chooser!
463 table.setValueAt(new FeatureColour(colorChooser.getColor()),
464 rowSelected, COLOUR_COLUMN);
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 // TODO: JAL-3048 relies on Castor XML parsing: not needed for JS-jalview core
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 // TODO: JAL-3048 not needed for Jalview-JS - save colours
935 JalviewFileChooser chooser = new JalviewFileChooser("fc",
936 SEQUENCE_FEATURE_COLOURS);
937 chooser.setFileView(new JalviewFileView());
938 chooser.setDialogTitle(
939 MessageManager.getString("label.save_feature_colours"));
940 chooser.setToolTipText(MessageManager.getString("action.save"));
942 int value = chooser.showSaveDialog(this);
944 if (value == JalviewFileChooser.APPROVE_OPTION)
946 save(chooser.getSelectedFile());
951 * Saves feature colours and filters to the given file
957 JalviewUserColours ucs = new JalviewUserColours();
958 ucs.setSchemeName("Sequence Features");
961 PrintWriter out = new PrintWriter(new OutputStreamWriter(
962 new FileOutputStream(file), "UTF-8"));
965 * sort feature types by colour order, from 0 (highest)
968 Set<String> fr_colours = fr.getAllFeatureColours();
969 String[] sortedTypes = fr_colours
970 .toArray(new String[fr_colours.size()]);
971 Arrays.sort(sortedTypes, new Comparator<String>()
974 public int compare(String type1, String type2)
976 return Float.compare(fr.getOrder(type1), fr.getOrder(type2));
981 * save feature colours
983 for (String featureType : sortedTypes)
985 FeatureColourI fcol = fr.getFeatureStyle(featureType);
986 jalview.schemabinding.version2.Colour col = Jalview2XML.marshalColour(
992 * save any feature filters
994 for (String featureType : sortedTypes)
996 FeatureMatcherSetI filter = fr.getFeatureFilter(featureType);
997 if (filter != null && !filter.isEmpty())
999 Iterator<FeatureMatcherI> iterator = filter.getMatchers().iterator();
1000 FeatureMatcherI firstMatcher = iterator.next();
1001 MatcherSet ms = Jalview2XML.marshalFilter(firstMatcher, iterator,
1003 Filter filterModel = new Filter();
1004 filterModel.setFeatureType(featureType);
1005 filterModel.setMatcherSet(ms);
1006 ucs.addFilter(filterModel);
1012 } catch (Exception ex)
1014 ex.printStackTrace();
1018 public void invertSelection()
1020 Object[][] data = ((FeatureTableModel) table.getModel()).getData();
1021 for (int i = 0; i < data.length; i++)
1023 data[i][SHOW_COLUMN] = !(Boolean) data[i][SHOW_COLUMN];
1025 updateFeatureRenderer(data, true);
1029 public void orderByAvWidth()
1031 if (table == null || table.getModel() == null)
1035 Object[][] data = ((FeatureTableModel) table.getModel()).getData();
1036 float[] width = new float[data.length];
1040 for (int i = 0; i < data.length; i++)
1042 awidth = typeWidth.get(data[i][TYPE_COLUMN]);
1045 width[i] = awidth[1] / awidth[0];// *awidth[0]*awidth[2]; - better
1046 // weight - but have to make per
1047 // sequence, too (awidth[2])
1048 // if (width[i]==1) // hack to distinguish single width sequences.
1059 boolean sort = false;
1060 for (int i = 0; i < width.length; i++)
1062 // awidth = (float[]) typeWidth.get(data[i][0]);
1065 width[i] = fr.getOrder(data[i][TYPE_COLUMN].toString());
1068 width[i] = fr.setOrder(data[i][TYPE_COLUMN].toString(),
1074 width[i] /= max; // normalize
1075 fr.setOrder(data[i][TYPE_COLUMN].toString(), width[i]); // store for later
1079 sort = sort || width[i - 1] > width[i];
1084 jalview.util.QuickSort.sort(width, data);
1085 // update global priority order
1088 updateFeatureRenderer(data, false);
1096 frame.setClosed(true);
1097 } catch (Exception exe)
1103 public void updateFeatureRenderer(Object[][] data)
1105 updateFeatureRenderer(data, true);
1109 * Update the priority order of features; only repaint if this changed the order
1110 * of visible features
1115 void updateFeatureRenderer(Object[][] data, boolean visibleNew)
1117 FeatureSettingsBean[] rowData = getTableAsBeans(data);
1119 if (fr.setFeaturePriority(rowData, visibleNew))
1121 af.alignPanel.paintAlignment(true, true);
1126 * Converts table data into an array of data beans
1128 private FeatureSettingsBean[] getTableAsBeans(Object[][] data)
1130 FeatureSettingsBean[] rowData = new FeatureSettingsBean[data.length];
1131 for (int i = 0; i < data.length; i++)
1133 String type = (String) data[i][TYPE_COLUMN];
1134 FeatureColourI colour = (FeatureColourI) data[i][COLOUR_COLUMN];
1135 FeatureMatcherSetI theFilter = (FeatureMatcherSetI) data[i][FILTER_COLUMN];
1136 Boolean isShown = (Boolean) data[i][SHOW_COLUMN];
1137 rowData[i] = new FeatureSettingsBean(type, colour, theFilter,
1143 private void jbInit() throws Exception
1145 this.setLayout(new BorderLayout());
1147 JPanel settingsPane = new JPanel();
1148 settingsPane.setLayout(new BorderLayout());
1150 JPanel bigPanel = new JPanel();
1151 bigPanel.setLayout(new BorderLayout());
1153 groupPanel = new JPanel();
1154 bigPanel.add(groupPanel, BorderLayout.NORTH);
1156 JButton invert = new JButton(
1157 MessageManager.getString("label.invert_selection"));
1158 invert.setFont(JvSwingUtils.getLabelFont());
1159 invert.addActionListener(new ActionListener()
1162 public void actionPerformed(ActionEvent e)
1168 JButton optimizeOrder = new JButton(
1169 MessageManager.getString("label.optimise_order"));
1170 optimizeOrder.setFont(JvSwingUtils.getLabelFont());
1171 optimizeOrder.addActionListener(new ActionListener()
1174 public void actionPerformed(ActionEvent e)
1180 JButton sortByScore = new JButton(
1181 MessageManager.getString("label.seq_sort_by_score"));
1182 sortByScore.setFont(JvSwingUtils.getLabelFont());
1183 sortByScore.addActionListener(new ActionListener()
1186 public void actionPerformed(ActionEvent e)
1188 af.avc.sortAlignmentByFeatureScore(null);
1191 JButton sortByDens = new JButton(
1192 MessageManager.getString("label.sequence_sort_by_density"));
1193 sortByDens.setFont(JvSwingUtils.getLabelFont());
1194 sortByDens.addActionListener(new ActionListener()
1197 public void actionPerformed(ActionEvent e)
1199 af.avc.sortAlignmentByFeatureDensity(null);
1203 JButton help = new JButton(MessageManager.getString("action.help"));
1204 help.setFont(JvSwingUtils.getLabelFont());
1205 help.addActionListener(new ActionListener()
1208 public void actionPerformed(ActionEvent e)
1212 Help.showHelpWindow(HelpId.SequenceFeatureSettings);
1213 } catch (HelpSetException e1)
1215 e1.printStackTrace();
1219 help.setFont(JvSwingUtils.getLabelFont());
1220 help.setText(MessageManager.getString("action.help"));
1221 help.addActionListener(new ActionListener()
1224 public void actionPerformed(ActionEvent e)
1228 Help.showHelpWindow(HelpId.SequenceFeatureSettings);
1229 } catch (HelpSetException e1)
1231 e1.printStackTrace();
1236 JButton cancel = new JButton(MessageManager.getString("action.cancel"));
1237 cancel.setFont(JvSwingUtils.getLabelFont());
1238 cancel.addActionListener(new ActionListener()
1241 public void actionPerformed(ActionEvent e)
1243 fr.setTransparency(originalTransparency);
1244 fr.setFeatureFilters(originalFilters);
1245 updateFeatureRenderer(originalData);
1250 JButton ok = new JButton(MessageManager.getString("action.ok"));
1251 ok.setFont(JvSwingUtils.getLabelFont());
1252 ok.addActionListener(new ActionListener()
1255 public void actionPerformed(ActionEvent e)
1261 JButton loadColours = new JButton(
1262 MessageManager.getString("label.load_colours"));
1263 loadColours.setFont(JvSwingUtils.getLabelFont());
1264 loadColours.setToolTipText(
1265 MessageManager.getString("label.load_colours_tooltip"));
1266 loadColours.addActionListener(new ActionListener()
1269 public void actionPerformed(ActionEvent e)
1275 JButton saveColours = new JButton(
1276 MessageManager.getString("label.save_colours"));
1277 saveColours.setFont(JvSwingUtils.getLabelFont());
1278 saveColours.setToolTipText(
1279 MessageManager.getString("label.save_colours_tooltip"));
1280 saveColours.addActionListener(new ActionListener()
1283 public void actionPerformed(ActionEvent e)
1288 transparency.addChangeListener(new ChangeListener()
1291 public void stateChanged(ChangeEvent evt)
1293 if (!inConstruction)
1295 fr.setTransparency((100 - transparency.getValue()) / 100f);
1296 af.alignPanel.paintAlignment(true, true);
1301 transparency.setMaximum(70);
1302 transparency.setToolTipText(
1303 MessageManager.getString("label.transparency_tip"));
1305 JPanel transPanel = new JPanel(new GridLayout(1, 2));
1306 bigPanel.add(transPanel, BorderLayout.SOUTH);
1308 JPanel transbuttons = new JPanel(new GridLayout(5, 1));
1309 transbuttons.add(optimizeOrder);
1310 transbuttons.add(invert);
1311 transbuttons.add(sortByScore);
1312 transbuttons.add(sortByDens);
1313 transbuttons.add(help);
1314 transPanel.add(transparency);
1315 transPanel.add(transbuttons);
1317 JPanel buttonPanel = new JPanel();
1318 buttonPanel.add(ok);
1319 buttonPanel.add(cancel);
1320 buttonPanel.add(loadColours);
1321 buttonPanel.add(saveColours);
1322 bigPanel.add(scrollPane, BorderLayout.CENTER);
1323 settingsPane.add(bigPanel, BorderLayout.CENTER);
1324 settingsPane.add(buttonPanel, BorderLayout.SOUTH);
1325 this.add(settingsPane);
1328 // ///////////////////////////////////////////////////////////////////////
1329 // http://java.sun.com/docs/books/tutorial/uiswing/components/table.html
1330 // ///////////////////////////////////////////////////////////////////////
1331 class FeatureTableModel extends AbstractTableModel
1333 private String[] columnNames = {
1334 MessageManager.getString("label.feature_type"),
1335 MessageManager.getString("action.colour"),
1336 MessageManager.getString("label.filter"),
1337 MessageManager.getString("label.show") };
1339 private Object[][] data;
1341 FeatureTableModel(Object[][] data)
1346 public Object[][] getData()
1351 public void setData(Object[][] data)
1357 public int getColumnCount()
1359 return columnNames.length;
1362 public Object[] getRow(int row)
1368 public int getRowCount()
1374 public String getColumnName(int col)
1376 return columnNames[col];
1380 public Object getValueAt(int row, int col)
1382 return data[row][col];
1386 * Answers the class of the object in column c of the first row of the table
1389 public Class<?> getColumnClass(int c)
1391 Object v = getValueAt(0, c);
1392 return v == null ? null : v.getClass();
1396 public boolean isCellEditable(int row, int col)
1398 return col == 0 ? false : true;
1402 public void setValueAt(Object value, int row, int col)
1404 data[row][col] = value;
1405 fireTableCellUpdated(row, col);
1406 updateFeatureRenderer(data);
1411 class ColorRenderer extends JLabel implements TableCellRenderer
1413 javax.swing.border.Border unselectedBorder = null;
1415 javax.swing.border.Border selectedBorder = null;
1417 final String baseTT = "Click to edit, right/apple click for menu.";
1419 public ColorRenderer()
1421 setOpaque(true); // MUST do this for background to show up.
1422 setHorizontalTextPosition(SwingConstants.CENTER);
1423 setVerticalTextPosition(SwingConstants.CENTER);
1427 public Component getTableCellRendererComponent(JTable tbl, Object color,
1428 boolean isSelected, boolean hasFocus, int row, int column)
1430 FeatureColourI cellColour = (FeatureColourI) color;
1432 setToolTipText(baseTT);
1433 setBackground(tbl.getBackground());
1434 if (!cellColour.isSimpleColour())
1436 Rectangle cr = tbl.getCellRect(row, column, false);
1437 FeatureSettings.renderGraduatedColor(this, cellColour,
1438 (int) cr.getWidth(), (int) cr.getHeight());
1444 setBackground(cellColour.getColour());
1448 if (selectedBorder == null)
1450 selectedBorder = BorderFactory.createMatteBorder(2, 5, 2, 5,
1451 tbl.getSelectionBackground());
1453 setBorder(selectedBorder);
1457 if (unselectedBorder == null)
1459 unselectedBorder = BorderFactory.createMatteBorder(2, 5, 2, 5,
1460 tbl.getBackground());
1462 setBorder(unselectedBorder);
1469 class FilterRenderer extends JLabel implements TableCellRenderer
1471 javax.swing.border.Border unselectedBorder = null;
1473 javax.swing.border.Border selectedBorder = null;
1475 public FilterRenderer()
1477 setOpaque(true); // MUST do this for background to show up.
1478 setHorizontalTextPosition(SwingConstants.CENTER);
1479 setVerticalTextPosition(SwingConstants.CENTER);
1483 public Component getTableCellRendererComponent(JTable tbl,
1484 Object filter, boolean isSelected, boolean hasFocus, int row,
1487 FeatureMatcherSetI theFilter = (FeatureMatcherSetI) filter;
1489 String asText = theFilter.toString();
1490 setBackground(tbl.getBackground());
1491 this.setText(asText);
1496 if (selectedBorder == null)
1498 selectedBorder = BorderFactory.createMatteBorder(2, 5, 2, 5,
1499 tbl.getSelectionBackground());
1501 setBorder(selectedBorder);
1505 if (unselectedBorder == null)
1507 unselectedBorder = BorderFactory.createMatteBorder(2, 5, 2, 5,
1508 tbl.getBackground());
1510 setBorder(unselectedBorder);
1518 * update comp using rendering settings from gcol
1523 public static void renderGraduatedColor(JLabel comp, FeatureColourI gcol)
1525 int w = comp.getWidth(), h = comp.getHeight();
1528 w = (int) comp.getPreferredSize().getWidth();
1529 h = (int) comp.getPreferredSize().getHeight();
1536 renderGraduatedColor(comp, gcol, w, h);
1539 public static void renderGraduatedColor(JLabel comp, FeatureColourI gcol,
1542 boolean thr = false;
1543 StringBuilder tt = new StringBuilder();
1544 StringBuilder tx = new StringBuilder();
1546 if (gcol.isColourByAttribute())
1548 tx.append(String.join(":", gcol.getAttributeName()));
1550 else if (!gcol.isColourByLabel())
1552 tx.append(MessageManager.getString("label.score"));
1555 if (gcol.isAboveThreshold())
1559 tt.append("Thresholded (Above ").append(gcol.getThreshold())
1562 if (gcol.isBelowThreshold())
1566 tt.append("Thresholded (Below ").append(gcol.getThreshold())
1569 if (gcol.isColourByLabel())
1571 tt.append("Coloured by label text. ").append(tt);
1576 if (!gcol.isColourByAttribute())
1584 Color newColor = gcol.getMaxColour();
1585 comp.setBackground(newColor);
1586 // System.err.println("Width is " + w / 2);
1587 Icon ficon = new FeatureIcon(gcol, comp.getBackground(), w, h, thr);
1588 comp.setIcon(ficon);
1589 // tt+="RGB value: Max (" + newColor.getRed() + ", "
1590 // + newColor.getGreen() + ", " + newColor.getBlue()
1591 // + ")\nMin (" + minCol.getRed() + ", " + minCol.getGreen()
1592 // + ", " + minCol.getBlue() + ")");
1594 comp.setHorizontalAlignment(SwingConstants.CENTER);
1595 comp.setText(tx.toString());
1596 if (tt.length() > 0)
1598 if (comp.getToolTipText() == null)
1600 comp.setToolTipText(tt.toString());
1604 comp.setToolTipText(
1605 tt.append(" ").append(comp.getToolTipText()).toString());
1610 class ColorEditor extends AbstractCellEditor
1611 implements TableCellEditor, ActionListener
1615 FeatureColourI currentColor;
1617 FeatureTypeSettings chooser;
1623 JColorChooser colorChooser;
1627 protected static final String EDIT = "edit";
1629 int rowSelected = 0;
1631 public ColorEditor(FeatureSettings fs)
1634 // Set up the editor (from the table's point of view),
1635 // which is a button.
1636 // This button brings up the color chooser dialog,
1637 // which is the editor from the user's point of view.
1638 button = new JButton();
1639 button.setActionCommand(EDIT);
1640 button.addActionListener(this);
1641 button.setBorderPainted(false);
1642 // Set up the dialog that the button brings up.
1643 colorChooser = new JColorChooser();
1644 dialog = JColorChooser.createDialog(button,
1645 MessageManager.getString("label.select_colour"), true, // modal
1646 colorChooser, this, // OK button handler
1647 null); // no CANCEL button handler
1651 * Handles events from the editor button and from the dialog's OK button.
1654 public void actionPerformed(ActionEvent e)
1656 // todo test e.getSource() instead here
1657 if (EDIT.equals(e.getActionCommand()))
1659 // The user has clicked the cell, so
1660 // bring up the dialog.
1661 if (currentColor.isSimpleColour())
1663 // bring up simple color chooser
1664 button.setBackground(currentColor.getColour());
1665 colorChooser.setColor(currentColor.getColour());
1666 dialog.setVisible(true);
1670 // bring up graduated chooser.
1671 chooser = new FeatureTypeSettings(me.fr, type);
1672 chooser.setRequestFocusEnabled(true);
1673 chooser.requestFocus();
1674 chooser.addActionListener(this);
1675 chooser.showTab(true);
1677 // Make the renderer reappear.
1678 fireEditingStopped();
1683 if (currentColor.isSimpleColour())
1686 * read off colour picked in colour chooser after OK pressed
1688 currentColor = new FeatureColour(colorChooser.getColor());
1689 me.table.setValueAt(currentColor, rowSelected, COLOUR_COLUMN);
1694 * after OK in variable colour dialog, any changes to colour
1695 * (or filters!) are already set in FeatureRenderer, so just
1696 * update table data without triggering updateFeatureRenderer
1698 currentColor = fr.getFeatureColours().get(type);
1699 FeatureMatcherSetI currentFilter = me.fr.getFeatureFilter(type);
1700 if (currentFilter == null)
1702 currentFilter = new FeatureMatcherSet();
1704 Object[] data = ((FeatureTableModel) table.getModel())
1705 .getData()[rowSelected];
1706 data[COLOUR_COLUMN] = currentColor;
1707 data[FILTER_COLUMN] = currentFilter;
1709 fireEditingStopped();
1710 me.table.validate();
1714 // Implement the one CellEditor method that AbstractCellEditor doesn't.
1716 public Object getCellEditorValue()
1718 return currentColor;
1721 // Implement the one method defined by TableCellEditor.
1723 public Component getTableCellEditorComponent(JTable theTable, Object value,
1724 boolean isSelected, int row, int column)
1726 currentColor = (FeatureColourI) value;
1727 this.rowSelected = row;
1728 type = me.table.getValueAt(row, TYPE_COLUMN).toString();
1729 button.setOpaque(true);
1730 button.setBackground(me.getBackground());
1731 if (!currentColor.isSimpleColour())
1733 JLabel btn = new JLabel();
1734 btn.setSize(button.getSize());
1735 FeatureSettings.renderGraduatedColor(btn, currentColor);
1736 button.setBackground(btn.getBackground());
1737 button.setIcon(btn.getIcon());
1738 button.setText(btn.getText());
1743 button.setIcon(null);
1744 button.setBackground(currentColor.getColour());
1751 * The cell editor for the Filter column. It displays the text of any filters
1752 * for the feature type in that row (in full as a tooltip, possible abbreviated
1753 * as display text). On click in the cell, opens the Feature Display Settings
1754 * dialog at the Filters tab.
1756 class FilterEditor extends AbstractCellEditor
1757 implements TableCellEditor, ActionListener
1761 FeatureMatcherSetI currentFilter;
1769 protected static final String EDIT = "edit";
1771 int rowSelected = 0;
1773 public FilterEditor(FeatureSettings me)
1776 button = new JButton();
1777 button.setActionCommand(EDIT);
1778 button.addActionListener(this);
1779 button.setBorderPainted(false);
1783 * Handles events from the editor button
1786 public void actionPerformed(ActionEvent e)
1788 if (button == e.getSource())
1790 FeatureTypeSettings chooser = new FeatureTypeSettings(me.fr, type);
1791 chooser.addActionListener(this);
1792 chooser.setRequestFocusEnabled(true);
1793 chooser.requestFocus();
1794 if (lastLocation != null)
1796 // todo open at its last position on screen
1797 chooser.setBounds(lastLocation.x, lastLocation.y,
1798 chooser.getWidth(), chooser.getHeight());
1801 chooser.showTab(false);
1802 fireEditingStopped();
1804 else if (e.getSource() instanceof Component)
1808 * after OK in variable colour dialog, any changes to filter
1809 * (or colours!) are already set in FeatureRenderer, so just
1810 * update table data without triggering updateFeatureRenderer
1812 FeatureColourI currentColor = fr.getFeatureColours().get(type);
1813 currentFilter = me.fr.getFeatureFilter(type);
1814 if (currentFilter == null)
1816 currentFilter = new FeatureMatcherSet();
1818 Object[] data = ((FeatureTableModel) table.getModel())
1819 .getData()[rowSelected];
1820 data[COLOUR_COLUMN] = currentColor;
1821 data[FILTER_COLUMN] = currentFilter;
1822 fireEditingStopped();
1823 me.table.validate();
1828 public Object getCellEditorValue()
1830 return currentFilter;
1834 public Component getTableCellEditorComponent(JTable theTable, Object value,
1835 boolean isSelected, int row, int column)
1837 currentFilter = (FeatureMatcherSetI) value;
1838 this.rowSelected = row;
1839 type = me.table.getValueAt(row, TYPE_COLUMN).toString();
1840 button.setOpaque(true);
1841 button.setBackground(me.getBackground());
1842 button.setText(currentFilter.toString());
1843 button.setToolTipText(currentFilter.toString());
1844 button.setIcon(null);
1850 class FeatureIcon implements Icon
1852 FeatureColourI gcol;
1856 boolean midspace = false;
1858 int width = 50, height = 20;
1860 int s1, e1; // start and end of midpoint band for thresholded symbol
1862 Color mpcolour = Color.white;
1864 FeatureIcon(FeatureColourI gfc, Color bg, int w, int h, boolean mspace)
1884 public int getIconWidth()
1890 public int getIconHeight()
1896 public void paintIcon(Component c, Graphics g, int x, int y)
1899 if (gcol.isColourByLabel())
1902 g.fillRect(0, 0, width, height);
1903 // need an icon here.
1904 g.setColor(gcol.getMaxColour());
1906 g.setFont(new Font("Verdana", Font.PLAIN, 9));
1908 // g.setFont(g.getFont().deriveFont(
1909 // AffineTransform.getScaleInstance(
1910 // width/g.getFontMetrics().stringWidth("Label"),
1911 // height/g.getFontMetrics().getHeight())));
1913 g.drawString(MessageManager.getString("label.label"), 0, 0);
1918 Color minCol = gcol.getMinColour();
1920 g.fillRect(0, 0, s1, height);
1923 g.setColor(Color.white);
1924 g.fillRect(s1, 0, e1 - s1, height);
1926 g.setColor(gcol.getMaxColour());
1927 g.fillRect(0, e1, width - e1, height);