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.gui.JalviewColourChooser.ColourChooserListener;
32 import jalview.io.JalviewFileChooser;
33 import jalview.io.JalviewFileView;
34 import jalview.schemabinding.version2.Filter;
35 import jalview.schemabinding.version2.JalviewUserColours;
36 import jalview.schemabinding.version2.MatcherSet;
37 import jalview.schemes.FeatureColour;
38 import jalview.util.MessageManager;
39 import jalview.util.Platform;
40 import jalview.viewmodel.seqfeatures.FeatureRendererModel.FeatureSettingsBean;
42 import java.awt.BorderLayout;
43 import java.awt.Color;
44 import java.awt.Component;
45 import java.awt.Dimension;
47 import java.awt.Graphics;
48 import java.awt.GridLayout;
49 import java.awt.Point;
50 import java.awt.Rectangle;
51 import java.awt.event.ActionEvent;
52 import java.awt.event.ActionListener;
53 import java.awt.event.ItemEvent;
54 import java.awt.event.ItemListener;
55 import java.awt.event.MouseAdapter;
56 import java.awt.event.MouseEvent;
57 import java.awt.event.MouseMotionAdapter;
58 import java.beans.PropertyChangeEvent;
59 import java.beans.PropertyChangeListener;
61 import java.io.FileInputStream;
62 import java.io.FileOutputStream;
63 import java.io.InputStreamReader;
64 import java.io.OutputStreamWriter;
65 import java.io.PrintWriter;
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 float originalTransparency;
137 Map<String, FeatureMatcherSetI> originalFilters;
139 final JInternalFrame frame;
141 JScrollPane scrollPane = new JScrollPane();
147 JSlider transparency = new JSlider();
150 * when true, constructor is still executing - so ignore UI events
152 protected volatile boolean inConstruction = true;
154 int selectedRow = -1;
156 JButton fetchDAS = new JButton();
158 JButton saveDAS = new JButton();
160 JButton cancelDAS = new JButton();
162 boolean resettingTable = false;
165 * true when Feature Settings are updating from feature renderer
167 boolean handlingUpdate = false;
170 * holds {featureCount, totalExtent} for each feature type
172 Map<String, float[]> typeWidth = null;
179 public FeatureSettings(AlignFrame alignFrame)
181 this.af = alignFrame;
182 fr = af.getFeatureRenderer();
184 // save transparency for restore on Cancel
185 originalTransparency = fr.getTransparency();
186 int originalTransparencyAsPercent = (int) (originalTransparency * 100);
187 transparency.setMaximum(100 - originalTransparencyAsPercent);
189 originalFilters = new HashMap<>(fr.getFeatureFilters()); // shallow copy
194 } catch (Exception ex)
196 ex.printStackTrace();
202 public String getToolTipText(MouseEvent e)
205 int column = table.columnAtPoint(e.getPoint());
209 tip = JvSwingUtils.wrapTooltip(true, MessageManager
210 .getString("label.feature_settings_click_drag"));
213 int row = table.rowAtPoint(e.getPoint());
214 FeatureMatcherSet o = (FeatureMatcherSet) table.getValueAt(row,
217 ? MessageManager.getString("label.filters_tooltip")
226 table.getTableHeader().setFont(new Font("Verdana", Font.PLAIN, 12));
227 table.setFont(new Font("Verdana", Font.PLAIN, 12));
229 // table.setDefaultRenderer(Color.class, new ColorRenderer());
230 // table.setDefaultEditor(Color.class, new ColorEditor(this));
232 table.setDefaultEditor(FeatureColour.class, new ColorEditor(this));
233 table.setDefaultRenderer(FeatureColour.class, new ColorRenderer());
235 table.setDefaultEditor(FeatureMatcherSet.class, new FilterEditor(this));
236 table.setDefaultRenderer(FeatureMatcherSet.class, new FilterRenderer());
238 TableColumn colourColumn = new TableColumn(COLOUR_COLUMN, 75,
239 new ColorRenderer(), new ColorEditor(this));
240 table.addColumn(colourColumn);
242 TableColumn filterColumn = new TableColumn(FILTER_COLUMN, 75,
243 new FilterRenderer(), new FilterEditor(this));
244 table.addColumn(filterColumn);
246 table.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
248 table.addMouseListener(new MouseAdapter()
251 public void mousePressed(MouseEvent evt)
253 selectedRow = table.rowAtPoint(evt.getPoint());
254 String type = (String) table.getValueAt(selectedRow, TYPE_COLUMN);
255 if (evt.isPopupTrigger())
257 Object colour = table.getValueAt(selectedRow, COLOUR_COLUMN);
258 popupSort(selectedRow, type, colour, fr.getMinMax(), evt.getX(),
261 else if (evt.getClickCount() == 2)
263 boolean invertSelection = evt.isAltDown();
264 boolean toggleSelection = Platform.isControlDown(evt);
265 boolean extendSelection = evt.isShiftDown();
266 fr.ap.alignFrame.avc.markColumnsContainingFeatures(
267 invertSelection, extendSelection, toggleSelection, type);
271 // isPopupTrigger fires on mouseReleased on Windows
273 public void mouseReleased(MouseEvent evt)
275 selectedRow = table.rowAtPoint(evt.getPoint());
276 if (evt.isPopupTrigger())
278 String type = (String) table.getValueAt(selectedRow, TYPE_COLUMN);
279 Object colour = table.getValueAt(selectedRow, COLOUR_COLUMN);
280 popupSort(selectedRow, type, colour, fr.getMinMax(), evt.getX(),
286 table.addMouseMotionListener(new MouseMotionAdapter()
289 public void mouseDragged(MouseEvent evt)
291 int newRow = table.rowAtPoint(evt.getPoint());
292 if (newRow != selectedRow && selectedRow != -1 && newRow != -1)
295 * reposition 'selectedRow' to 'newRow' (the dragged to location)
296 * this could be more than one row away for a very fast drag action
297 * so just swap it with adjacent rows until we get it there
299 Object[][] data = ((FeatureTableModel) table.getModel())
301 int direction = newRow < selectedRow ? -1 : 1;
302 for (int i = selectedRow; i != newRow; i += direction)
304 Object[] temp = data[i];
305 data[i] = data[i + direction];
306 data[i + direction] = temp;
308 updateFeatureRenderer(data);
310 selectedRow = newRow;
314 // table.setToolTipText(JvSwingUtils.wrapTooltip(true,
315 // MessageManager.getString("label.feature_settings_click_drag")));
316 scrollPane.setViewportView(table);
318 if (af.getViewport().isShowSequenceFeatures() || !fr.hasRenderOrder())
320 fr.findAllFeatures(true); // display everything!
323 discoverAllFeatureData();
324 final PropertyChangeListener change;
325 final FeatureSettings fs = this;
326 fr.addPropertyChangeListener(change = new PropertyChangeListener()
329 public void propertyChange(PropertyChangeEvent evt)
331 if (!fs.resettingTable && !fs.handlingUpdate)
333 fs.handlingUpdate = true;
335 // new groups may be added with new sequence feature types only
336 fs.handlingUpdate = false;
342 frame = new JInternalFrame();
343 frame.setContentPane(this);
344 if (Platform.isAMac())
346 Desktop.addInternalFrame(frame,
347 MessageManager.getString("label.sequence_feature_settings"),
352 Desktop.addInternalFrame(frame,
353 MessageManager.getString("label.sequence_feature_settings"),
356 frame.setMinimumSize(new Dimension(MIN_WIDTH, MIN_HEIGHT));
358 frame.addInternalFrameListener(
359 new javax.swing.event.InternalFrameAdapter()
362 public void internalFrameClosed(
363 javax.swing.event.InternalFrameEvent evt)
365 fr.removePropertyChangeListener(change);
368 frame.setLayer(JLayeredPane.PALETTE_LAYER);
369 inConstruction = false;
372 protected void popupSort(final int rowSelected, final String type,
373 final Object typeCol, final Map<String, float[][]> minmax, int x,
376 final FeatureColourI featureColour = (FeatureColourI) typeCol;
378 JPopupMenu men = new JPopupMenu(MessageManager
379 .formatMessage("label.settings_for_param", new String[]
381 JMenuItem scr = new JMenuItem(
382 MessageManager.getString("label.sort_by_score"));
384 final FeatureSettings me = this;
385 scr.addActionListener(new ActionListener()
389 public void actionPerformed(ActionEvent e)
392 .sortAlignmentByFeatureScore(Arrays.asList(new String[]
397 JMenuItem dens = new JMenuItem(
398 MessageManager.getString("label.sort_by_density"));
399 dens.addActionListener(new ActionListener()
403 public void actionPerformed(ActionEvent e)
406 .sortAlignmentByFeatureDensity(Arrays.asList(new String[]
414 * variable colour options include colour by label, by score,
415 * by selected attribute text, or attribute value
417 final JCheckBoxMenuItem variableColourCB = new JCheckBoxMenuItem(
418 MessageManager.getString("label.variable_colour"));
419 variableColourCB.setSelected(!featureColour.isSimpleColour());
420 men.add(variableColourCB);
423 * checkbox action listener doubles up as listener to OK
424 * from the variable colour / filters dialog
426 variableColourCB.addActionListener(new ActionListener()
429 public void actionPerformed(ActionEvent e)
431 if (e.getSource() == variableColourCB)
433 if (featureColour.isSimpleColour())
436 * toggle simple colour to variable colour - show dialog
438 FeatureTypeSettings fc = new FeatureTypeSettings(me.fr, type);
439 fc.addActionListener(this);
444 * toggle variable to simple colour - show colour chooser
446 String title = MessageManager.getString("label.select_colour");
447 ColourChooserListener listener = new ColourChooserListener()
450 public void colourSelected(Color c)
452 table.setValueAt(new FeatureColour(c), rowSelected,
455 me.updateFeatureRenderer(
456 ((FeatureTableModel) table.getModel()).getData(),
460 JalviewColourChooser.showColourChooser(me, title, featureColour.getMaxColour(), listener);
464 if (e.getSource() instanceof FeatureTypeSettings)
467 * update after OK in feature colour dialog; the updated
468 * colour will have already been set in the FeatureRenderer
470 FeatureColourI fci = fr.getFeatureColours().get(type);
471 table.setValueAt(fci, rowSelected, COLOUR_COLUMN);
479 JMenuItem selCols = new JMenuItem(
480 MessageManager.getString("label.select_columns_containing"));
481 selCols.addActionListener(new ActionListener()
484 public void actionPerformed(ActionEvent arg0)
486 fr.ap.alignFrame.avc.markColumnsContainingFeatures(false, false,
490 JMenuItem clearCols = new JMenuItem(MessageManager
491 .getString("label.select_columns_not_containing"));
492 clearCols.addActionListener(new ActionListener()
495 public void actionPerformed(ActionEvent arg0)
497 fr.ap.alignFrame.avc.markColumnsContainingFeatures(true, false,
501 JMenuItem hideCols = new JMenuItem(
502 MessageManager.getString("label.hide_columns_containing"));
503 hideCols.addActionListener(new ActionListener()
506 public void actionPerformed(ActionEvent arg0)
508 fr.ap.alignFrame.hideFeatureColumns(type, true);
511 JMenuItem hideOtherCols = new JMenuItem(
512 MessageManager.getString("label.hide_columns_not_containing"));
513 hideOtherCols.addActionListener(new ActionListener()
516 public void actionPerformed(ActionEvent arg0)
518 fr.ap.alignFrame.hideFeatureColumns(type, false);
524 men.add(hideOtherCols);
525 men.show(table, x, y);
529 synchronized public void discoverAllFeatureData()
531 Set<String> allGroups = new HashSet<>();
532 AlignmentI alignment = af.getViewport().getAlignment();
534 for (int i = 0; i < alignment.getHeight(); i++)
536 SequenceI seq = alignment.getSequenceAt(i);
537 for (String group : seq.getFeatures().getFeatureGroups(true))
539 if (group != null && !allGroups.contains(group))
541 allGroups.add(group);
542 checkGroupState(group);
553 * Synchronise gui group list and check visibility of group
556 * @return true if group is visible
558 private boolean checkGroupState(String group)
560 boolean visible = fr.checkGroupVisibility(group, true);
562 for (int g = 0; g < groupPanel.getComponentCount(); g++)
564 if (((JCheckBox) groupPanel.getComponent(g)).getText().equals(group))
566 ((JCheckBox) groupPanel.getComponent(g)).setSelected(visible);
571 final String grp = group;
572 final JCheckBox check = new JCheckBox(group, visible);
573 check.setFont(new Font("Serif", Font.BOLD, 12));
574 check.setToolTipText(group);
575 check.addItemListener(new ItemListener()
578 public void itemStateChanged(ItemEvent evt)
580 fr.setGroupVisibility(check.getText(), check.isSelected());
581 resetTable(new String[] { grp });
582 af.alignPanel.paintAlignment(true, true);
585 groupPanel.add(check);
589 synchronized void resetTable(String[] groupChanged)
595 resettingTable = true;
596 typeWidth = new Hashtable<>();
597 // TODO: change avWidth calculation to 'per-sequence' average and use long
600 Set<String> displayableTypes = new HashSet<>();
601 Set<String> foundGroups = new HashSet<>();
604 * determine which feature types may be visible depending on
605 * which groups are selected, and recompute average width data
607 for (int i = 0; i < af.getViewport().getAlignment().getHeight(); i++)
610 SequenceI seq = af.getViewport().getAlignment().getSequenceAt(i);
613 * get the sequence's groups for positional features
614 * and keep track of which groups are visible
616 Set<String> groups = seq.getFeatures().getFeatureGroups(true);
617 Set<String> visibleGroups = new HashSet<>();
618 for (String group : groups)
620 if (group == null || checkGroupState(group))
622 visibleGroups.add(group);
625 foundGroups.addAll(groups);
628 * get distinct feature types for visible groups
629 * record distinct visible types, and their count and total length
631 Set<String> types = seq.getFeatures().getFeatureTypesForGroups(true,
632 visibleGroups.toArray(new String[visibleGroups.size()]));
633 for (String type : types)
635 displayableTypes.add(type);
636 float[] avWidth = typeWidth.get(type);
639 avWidth = new float[2];
640 typeWidth.put(type, avWidth);
642 // todo this could include features with a non-visible group
643 // - do we greatly care?
644 // todo should we include non-displayable features here, and only
645 // update when features are added?
646 avWidth[0] += seq.getFeatures().getFeatureCount(true, type);
647 avWidth[1] += seq.getFeatures().getTotalFeatureLength(type);
651 Object[][] data = new Object[displayableTypes.size()][COLUMN_COUNT];
654 if (fr.hasRenderOrder())
658 fr.findAllFeatures(groupChanged != null); // prod to update
659 // colourschemes. but don't
661 // First add the checks in the previous render order,
662 // in case the window has been closed and reopened
664 List<String> frl = fr.getRenderOrder();
665 for (int ro = frl.size() - 1; ro > -1; ro--)
667 String type = frl.get(ro);
669 if (!displayableTypes.contains(type))
674 data[dataIndex][TYPE_COLUMN] = type;
675 data[dataIndex][COLOUR_COLUMN] = fr.getFeatureStyle(type);
676 FeatureMatcherSetI featureFilter = fr.getFeatureFilter(type);
677 data[dataIndex][FILTER_COLUMN] = featureFilter == null
678 ? new FeatureMatcherSet()
680 data[dataIndex][SHOW_COLUMN] = new Boolean(
681 af.getViewport().getFeaturesDisplayed().isVisible(type));
683 displayableTypes.remove(type);
688 * process any extra features belonging only to
689 * a group which was just selected
691 while (!displayableTypes.isEmpty())
693 String type = displayableTypes.iterator().next();
694 data[dataIndex][TYPE_COLUMN] = type;
696 data[dataIndex][COLOUR_COLUMN] = fr.getFeatureStyle(type);
697 if (data[dataIndex][COLOUR_COLUMN] == null)
699 // "Colour has been updated in another view!!"
700 fr.clearRenderOrder();
703 FeatureMatcherSetI featureFilter = fr.getFeatureFilter(type);
704 data[dataIndex][FILTER_COLUMN] = featureFilter == null
705 ? new FeatureMatcherSet()
707 data[dataIndex][SHOW_COLUMN] = new Boolean(true);
709 displayableTypes.remove(type);
712 if (originalData == null)
714 originalData = new Object[data.length][COLUMN_COUNT];
715 for (int i = 0; i < data.length; i++)
717 System.arraycopy(data[i], 0, originalData[i], 0, COLUMN_COUNT);
722 updateOriginalData(data);
725 table.setModel(new FeatureTableModel(data));
726 table.getColumnModel().getColumn(0).setPreferredWidth(200);
728 groupPanel.setLayout(
729 new GridLayout(fr.getFeatureGroupsSize() / 4 + 1, 4));
730 pruneGroups(foundGroups);
731 groupPanel.validate();
733 updateFeatureRenderer(data, groupChanged != null);
734 resettingTable = false;
738 * Updates 'originalData' (used for restore on Cancel) if we detect that changes
739 * have been made outwith this dialog
741 * <li>a new feature type added (and made visible)</li>
742 * <li>a feature colour changed (in the Amend Features dialog)</li>
747 protected void updateOriginalData(Object[][] foundData)
749 // todo LinkedHashMap instead of Object[][] would be nice
751 Object[][] currentData = ((FeatureTableModel) table.getModel())
753 for (Object[] row : foundData)
755 String type = (String) row[TYPE_COLUMN];
756 boolean found = false;
757 for (Object[] current : currentData)
759 if (type.equals(current[TYPE_COLUMN]))
763 * currently dependent on object equality here;
764 * really need an equals method on FeatureColour
766 if (!row[COLOUR_COLUMN].equals(current[COLOUR_COLUMN]))
769 * feature colour has changed externally - update originalData
771 for (Object[] original : originalData)
773 if (type.equals(original[TYPE_COLUMN]))
775 original[COLOUR_COLUMN] = row[COLOUR_COLUMN];
786 * new feature detected - add to original data (on top)
788 Object[][] newData = new Object[originalData.length
790 for (int i = 0; i < originalData.length; i++)
792 System.arraycopy(originalData[i], 0, newData[i + 1], 0,
796 originalData = newData;
802 * Remove from the groups panel any checkboxes for groups that are not in the
803 * foundGroups set. This enables removing a group from the display when the last
804 * feature in that group is deleted.
808 protected void pruneGroups(Set<String> foundGroups)
810 for (int g = 0; g < groupPanel.getComponentCount(); g++)
812 JCheckBox checkbox = (JCheckBox) groupPanel.getComponent(g);
813 if (!foundGroups.contains(checkbox.getText()))
815 groupPanel.remove(checkbox);
821 * reorder data based on the featureRenderers global priority list.
825 private void ensureOrder(Object[][] data)
827 boolean sort = false;
828 float[] order = new float[data.length];
829 for (int i = 0; i < order.length; i++)
831 order[i] = fr.getOrder(data[i][0].toString());
834 order[i] = fr.setOrder(data[i][0].toString(), i / order.length);
838 sort = sort || order[i - 1] > order[i];
843 jalview.util.QuickSort.sort(order, data);
848 * Offers a file chooser dialog, and then loads the feature colours and
849 * filters from file in XML format and unmarshals to Jalview feature settings
853 // TODO: JAL-3048 relies on Castor XML parsing: not needed for JS-jalview core
856 JalviewFileChooser chooser = new JalviewFileChooser("fc",
857 SEQUENCE_FEATURE_COLOURS);
858 chooser.setFileView(new JalviewFileView());
859 chooser.setDialogTitle(
860 MessageManager.getString("label.load_feature_colours"));
861 chooser.setToolTipText(MessageManager.getString("action.load"));
863 int value = chooser.showOpenDialog(this);
865 if (value == JalviewFileChooser.APPROVE_OPTION)
867 File file = chooser.getSelectedFile();
873 * Loads feature colours and filters from XML stored in the given file
881 InputStreamReader in = new InputStreamReader(
882 new FileInputStream(file), "UTF-8");
884 JalviewUserColours jucs = JalviewUserColours.unmarshal(in);
887 * load feature colours
889 for (int i = jucs.getColourCount() - 1; i >= 0; i--)
891 jalview.schemabinding.version2.Colour newcol = jucs.getColour(i);
892 FeatureColourI colour = Jalview2XML.unmarshalColour(newcol);
893 fr.setColour(newcol.getName(), colour);
894 fr.setOrder(newcol.getName(), i / (float) jucs.getColourCount());
898 * load feature filters; loaded filters will replace any that are
899 * currently defined, other defined filters are left unchanged
901 for (int i = 0; i < jucs.getFilterCount(); i++)
903 jalview.schemabinding.version2.Filter filterModel = jucs
905 String featureType = filterModel.getFeatureType();
906 FeatureMatcherSetI filter = Jalview2XML.unmarshalFilter(featureType,
907 filterModel.getMatcherSet());
908 if (!filter.isEmpty())
910 fr.setFeatureFilter(featureType, filter);
915 * update feature settings table
920 Object[][] data = ((FeatureTableModel) table.getModel())
923 updateFeatureRenderer(data, false);
926 } catch (Exception ex)
928 System.out.println("Error loading User Colour File\n" + ex);
933 * Offers a file chooser dialog, and then saves the current feature colours
934 * and any filters to the selected file in XML format
938 // TODO: JAL-3048 not needed for Jalview-JS - save colours
939 JalviewFileChooser chooser = new JalviewFileChooser("fc",
940 SEQUENCE_FEATURE_COLOURS);
941 chooser.setFileView(new JalviewFileView());
942 chooser.setDialogTitle(
943 MessageManager.getString("label.save_feature_colours"));
944 chooser.setToolTipText(MessageManager.getString("action.save"));
946 int value = chooser.showSaveDialog(this);
948 if (value == JalviewFileChooser.APPROVE_OPTION)
950 save(chooser.getSelectedFile());
955 * Saves feature colours and filters to the given file
961 JalviewUserColours ucs = new JalviewUserColours();
962 ucs.setSchemeName("Sequence Features");
965 PrintWriter out = new PrintWriter(new OutputStreamWriter(
966 new FileOutputStream(file), "UTF-8"));
969 * sort feature types by colour order, from 0 (highest)
972 Set<String> fr_colours = fr.getAllFeatureColours();
973 String[] sortedTypes = fr_colours
974 .toArray(new String[fr_colours.size()]);
975 Arrays.sort(sortedTypes, new Comparator<String>()
978 public int compare(String type1, String type2)
980 return Float.compare(fr.getOrder(type1), fr.getOrder(type2));
985 * save feature colours
987 for (String featureType : sortedTypes)
989 FeatureColourI fcol = fr.getFeatureStyle(featureType);
990 jalview.schemabinding.version2.Colour col = Jalview2XML.marshalColour(
996 * save any feature filters
998 for (String featureType : sortedTypes)
1000 FeatureMatcherSetI filter = fr.getFeatureFilter(featureType);
1001 if (filter != null && !filter.isEmpty())
1003 Iterator<FeatureMatcherI> iterator = filter.getMatchers().iterator();
1004 FeatureMatcherI firstMatcher = iterator.next();
1005 MatcherSet ms = Jalview2XML.marshalFilter(firstMatcher, iterator,
1007 Filter filterModel = new Filter();
1008 filterModel.setFeatureType(featureType);
1009 filterModel.setMatcherSet(ms);
1010 ucs.addFilter(filterModel);
1016 } catch (Exception ex)
1018 ex.printStackTrace();
1022 public void invertSelection()
1024 Object[][] data = ((FeatureTableModel) table.getModel()).getData();
1025 for (int i = 0; i < data.length; i++)
1027 data[i][SHOW_COLUMN] = !(Boolean) data[i][SHOW_COLUMN];
1029 updateFeatureRenderer(data, true);
1033 public void orderByAvWidth()
1035 if (table == null || table.getModel() == null)
1039 Object[][] data = ((FeatureTableModel) table.getModel()).getData();
1040 float[] width = new float[data.length];
1044 for (int i = 0; i < data.length; i++)
1046 awidth = typeWidth.get(data[i][TYPE_COLUMN]);
1049 width[i] = awidth[1] / awidth[0];// *awidth[0]*awidth[2]; - better
1050 // weight - but have to make per
1051 // sequence, too (awidth[2])
1052 // if (width[i]==1) // hack to distinguish single width sequences.
1063 boolean sort = false;
1064 for (int i = 0; i < width.length; i++)
1066 // awidth = (float[]) typeWidth.get(data[i][0]);
1069 width[i] = fr.getOrder(data[i][TYPE_COLUMN].toString());
1072 width[i] = fr.setOrder(data[i][TYPE_COLUMN].toString(),
1078 width[i] /= max; // normalize
1079 fr.setOrder(data[i][TYPE_COLUMN].toString(), width[i]); // store for later
1083 sort = sort || width[i - 1] > width[i];
1088 jalview.util.QuickSort.sort(width, data);
1089 // update global priority order
1092 updateFeatureRenderer(data, false);
1100 frame.setClosed(true);
1101 } catch (Exception exe)
1107 public void updateFeatureRenderer(Object[][] data)
1109 updateFeatureRenderer(data, true);
1113 * Update the priority order of features; only repaint if this changed the order
1114 * of visible features
1119 void updateFeatureRenderer(Object[][] data, boolean visibleNew)
1121 FeatureSettingsBean[] rowData = getTableAsBeans(data);
1123 if (fr.setFeaturePriority(rowData, visibleNew))
1125 af.alignPanel.paintAlignment(true, true);
1130 * Converts table data into an array of data beans
1132 private FeatureSettingsBean[] getTableAsBeans(Object[][] data)
1134 FeatureSettingsBean[] rowData = new FeatureSettingsBean[data.length];
1135 for (int i = 0; i < data.length; i++)
1137 String type = (String) data[i][TYPE_COLUMN];
1138 FeatureColourI colour = (FeatureColourI) data[i][COLOUR_COLUMN];
1139 FeatureMatcherSetI theFilter = (FeatureMatcherSetI) data[i][FILTER_COLUMN];
1140 Boolean isShown = (Boolean) data[i][SHOW_COLUMN];
1141 rowData[i] = new FeatureSettingsBean(type, colour, theFilter,
1147 private void jbInit() throws Exception
1149 this.setLayout(new BorderLayout());
1151 JPanel settingsPane = new JPanel();
1152 settingsPane.setLayout(new BorderLayout());
1154 JPanel bigPanel = new JPanel();
1155 bigPanel.setLayout(new BorderLayout());
1157 groupPanel = new JPanel();
1158 bigPanel.add(groupPanel, BorderLayout.NORTH);
1160 JButton invert = new JButton(
1161 MessageManager.getString("label.invert_selection"));
1162 invert.setFont(JvSwingUtils.getLabelFont());
1163 invert.addActionListener(new ActionListener()
1166 public void actionPerformed(ActionEvent e)
1172 JButton optimizeOrder = new JButton(
1173 MessageManager.getString("label.optimise_order"));
1174 optimizeOrder.setFont(JvSwingUtils.getLabelFont());
1175 optimizeOrder.addActionListener(new ActionListener()
1178 public void actionPerformed(ActionEvent e)
1184 JButton sortByScore = new JButton(
1185 MessageManager.getString("label.seq_sort_by_score"));
1186 sortByScore.setFont(JvSwingUtils.getLabelFont());
1187 sortByScore.addActionListener(new ActionListener()
1190 public void actionPerformed(ActionEvent e)
1192 af.avc.sortAlignmentByFeatureScore(null);
1195 JButton sortByDens = new JButton(
1196 MessageManager.getString("label.sequence_sort_by_density"));
1197 sortByDens.setFont(JvSwingUtils.getLabelFont());
1198 sortByDens.addActionListener(new ActionListener()
1201 public void actionPerformed(ActionEvent e)
1203 af.avc.sortAlignmentByFeatureDensity(null);
1207 JButton help = new JButton(MessageManager.getString("action.help"));
1208 help.setFont(JvSwingUtils.getLabelFont());
1209 help.addActionListener(new ActionListener()
1212 public void actionPerformed(ActionEvent e)
1216 Help.showHelpWindow(HelpId.SequenceFeatureSettings);
1217 } catch (HelpSetException e1)
1219 e1.printStackTrace();
1223 help.setFont(JvSwingUtils.getLabelFont());
1224 help.setText(MessageManager.getString("action.help"));
1225 help.addActionListener(new ActionListener()
1228 public void actionPerformed(ActionEvent e)
1232 Help.showHelpWindow(HelpId.SequenceFeatureSettings);
1233 } catch (HelpSetException e1)
1235 e1.printStackTrace();
1240 JButton cancel = new JButton(MessageManager.getString("action.cancel"));
1241 cancel.setFont(JvSwingUtils.getLabelFont());
1242 cancel.addActionListener(new ActionListener()
1245 public void actionPerformed(ActionEvent e)
1247 fr.setTransparency(originalTransparency);
1248 fr.setFeatureFilters(originalFilters);
1249 updateFeatureRenderer(originalData);
1254 JButton ok = new JButton(MessageManager.getString("action.ok"));
1255 ok.setFont(JvSwingUtils.getLabelFont());
1256 ok.addActionListener(new ActionListener()
1259 public void actionPerformed(ActionEvent e)
1265 JButton loadColours = new JButton(
1266 MessageManager.getString("label.load_colours"));
1267 loadColours.setFont(JvSwingUtils.getLabelFont());
1268 loadColours.setToolTipText(
1269 MessageManager.getString("label.load_colours_tooltip"));
1270 loadColours.addActionListener(new ActionListener()
1273 public void actionPerformed(ActionEvent e)
1279 JButton saveColours = new JButton(
1280 MessageManager.getString("label.save_colours"));
1281 saveColours.setFont(JvSwingUtils.getLabelFont());
1282 saveColours.setToolTipText(
1283 MessageManager.getString("label.save_colours_tooltip"));
1284 saveColours.addActionListener(new ActionListener()
1287 public void actionPerformed(ActionEvent e)
1292 transparency.addChangeListener(new ChangeListener()
1295 public void stateChanged(ChangeEvent evt)
1297 if (!inConstruction)
1299 fr.setTransparency((100 - transparency.getValue()) / 100f);
1300 af.alignPanel.paintAlignment(true, true);
1305 transparency.setMaximum(70);
1306 transparency.setToolTipText(
1307 MessageManager.getString("label.transparency_tip"));
1309 JPanel transPanel = new JPanel(new GridLayout(1, 2));
1310 bigPanel.add(transPanel, BorderLayout.SOUTH);
1312 JPanel transbuttons = new JPanel(new GridLayout(5, 1));
1313 transbuttons.add(optimizeOrder);
1314 transbuttons.add(invert);
1315 transbuttons.add(sortByScore);
1316 transbuttons.add(sortByDens);
1317 transbuttons.add(help);
1318 transPanel.add(transparency);
1319 transPanel.add(transbuttons);
1321 JPanel buttonPanel = new JPanel();
1322 buttonPanel.add(ok);
1323 buttonPanel.add(cancel);
1324 buttonPanel.add(loadColours);
1325 buttonPanel.add(saveColours);
1326 bigPanel.add(scrollPane, BorderLayout.CENTER);
1327 settingsPane.add(bigPanel, BorderLayout.CENTER);
1328 settingsPane.add(buttonPanel, BorderLayout.SOUTH);
1329 this.add(settingsPane);
1332 // ///////////////////////////////////////////////////////////////////////
1333 // http://java.sun.com/docs/books/tutorial/uiswing/components/table.html
1334 // ///////////////////////////////////////////////////////////////////////
1335 class FeatureTableModel extends AbstractTableModel
1337 private String[] columnNames = {
1338 MessageManager.getString("label.feature_type"),
1339 MessageManager.getString("action.colour"),
1340 MessageManager.getString("label.filter"),
1341 MessageManager.getString("label.show") };
1343 private Object[][] data;
1345 FeatureTableModel(Object[][] data)
1350 public Object[][] getData()
1355 public void setData(Object[][] data)
1361 public int getColumnCount()
1363 return columnNames.length;
1366 public Object[] getRow(int row)
1372 public int getRowCount()
1378 public String getColumnName(int col)
1380 return columnNames[col];
1384 public Object getValueAt(int row, int col)
1386 return data[row][col];
1390 * Answers the class of the object in column c of the first row of the table
1393 public Class<?> getColumnClass(int c)
1395 Object v = getValueAt(0, c);
1396 return v == null ? null : v.getClass();
1400 public boolean isCellEditable(int row, int col)
1402 return col == 0 ? false : true;
1406 public void setValueAt(Object value, int row, int col)
1408 data[row][col] = value;
1409 fireTableCellUpdated(row, col);
1410 updateFeatureRenderer(data);
1415 class ColorRenderer extends JLabel implements TableCellRenderer
1417 javax.swing.border.Border unselectedBorder = null;
1419 javax.swing.border.Border selectedBorder = null;
1421 final String baseTT = "Click to edit, right/apple click for menu.";
1423 public ColorRenderer()
1425 setOpaque(true); // MUST do this for background to show up.
1426 setHorizontalTextPosition(SwingConstants.CENTER);
1427 setVerticalTextPosition(SwingConstants.CENTER);
1431 public Component getTableCellRendererComponent(JTable tbl, Object color,
1432 boolean isSelected, boolean hasFocus, int row, int column)
1434 FeatureColourI cellColour = (FeatureColourI) color;
1436 setToolTipText(baseTT);
1437 setBackground(tbl.getBackground());
1438 if (!cellColour.isSimpleColour())
1440 Rectangle cr = tbl.getCellRect(row, column, false);
1441 FeatureSettings.renderGraduatedColor(this, cellColour,
1442 (int) cr.getWidth(), (int) cr.getHeight());
1448 setBackground(cellColour.getColour());
1452 if (selectedBorder == null)
1454 selectedBorder = BorderFactory.createMatteBorder(2, 5, 2, 5,
1455 tbl.getSelectionBackground());
1457 setBorder(selectedBorder);
1461 if (unselectedBorder == null)
1463 unselectedBorder = BorderFactory.createMatteBorder(2, 5, 2, 5,
1464 tbl.getBackground());
1466 setBorder(unselectedBorder);
1473 class FilterRenderer extends JLabel implements TableCellRenderer
1475 javax.swing.border.Border unselectedBorder = null;
1477 javax.swing.border.Border selectedBorder = null;
1479 public FilterRenderer()
1481 setOpaque(true); // MUST do this for background to show up.
1482 setHorizontalTextPosition(SwingConstants.CENTER);
1483 setVerticalTextPosition(SwingConstants.CENTER);
1487 public Component getTableCellRendererComponent(JTable tbl,
1488 Object filter, boolean isSelected, boolean hasFocus, int row,
1491 FeatureMatcherSetI theFilter = (FeatureMatcherSetI) filter;
1493 String asText = theFilter.toString();
1494 setBackground(tbl.getBackground());
1495 this.setText(asText);
1500 if (selectedBorder == null)
1502 selectedBorder = BorderFactory.createMatteBorder(2, 5, 2, 5,
1503 tbl.getSelectionBackground());
1505 setBorder(selectedBorder);
1509 if (unselectedBorder == null)
1511 unselectedBorder = BorderFactory.createMatteBorder(2, 5, 2, 5,
1512 tbl.getBackground());
1514 setBorder(unselectedBorder);
1522 * update comp using rendering settings from gcol
1527 public static void renderGraduatedColor(JLabel comp, FeatureColourI gcol)
1529 int w = comp.getWidth(), h = comp.getHeight();
1532 w = (int) comp.getPreferredSize().getWidth();
1533 h = (int) comp.getPreferredSize().getHeight();
1540 renderGraduatedColor(comp, gcol, w, h);
1543 public static void renderGraduatedColor(JLabel comp, FeatureColourI gcol,
1546 boolean thr = false;
1547 StringBuilder tt = new StringBuilder();
1548 StringBuilder tx = new StringBuilder();
1550 if (gcol.isColourByAttribute())
1552 tx.append(String.join(":", gcol.getAttributeName()));
1554 else if (!gcol.isColourByLabel())
1556 tx.append(MessageManager.getString("label.score"));
1559 if (gcol.isAboveThreshold())
1563 tt.append("Thresholded (Above ").append(gcol.getThreshold())
1566 if (gcol.isBelowThreshold())
1570 tt.append("Thresholded (Below ").append(gcol.getThreshold())
1573 if (gcol.isColourByLabel())
1575 tt.append("Coloured by label text. ").append(tt);
1580 if (!gcol.isColourByAttribute())
1588 Color newColor = gcol.getMaxColour();
1589 comp.setBackground(newColor);
1590 // System.err.println("Width is " + w / 2);
1591 Icon ficon = new FeatureIcon(gcol, comp.getBackground(), w, h, thr);
1592 comp.setIcon(ficon);
1593 // tt+="RGB value: Max (" + newColor.getRed() + ", "
1594 // + newColor.getGreen() + ", " + newColor.getBlue()
1595 // + ")\nMin (" + minCol.getRed() + ", " + minCol.getGreen()
1596 // + ", " + minCol.getBlue() + ")");
1598 comp.setHorizontalAlignment(SwingConstants.CENTER);
1599 comp.setText(tx.toString());
1600 if (tt.length() > 0)
1602 if (comp.getToolTipText() == null)
1604 comp.setToolTipText(tt.toString());
1608 comp.setToolTipText(
1609 tt.append(" ").append(comp.getToolTipText()).toString());
1614 class ColorEditor extends AbstractCellEditor
1615 implements TableCellEditor, ActionListener
1619 FeatureColourI currentColor;
1621 FeatureTypeSettings chooser;
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);
1645 * Handles events from the editor button, and from the colour/filters
1646 * dialog's OK button
1649 public void actionPerformed(ActionEvent e)
1651 if (button == e.getSource())
1653 if (currentColor.isSimpleColour())
1656 * simple colour chooser
1658 String ttl = MessageManager.getString("label.select_colour");
1659 ColourChooserListener listener = new ColourChooserListener() {
1661 public void colourSelected(Color c)
1663 currentColor = new FeatureColour(c);
1664 me.table.setValueAt(currentColor, rowSelected, COLOUR_COLUMN);
1667 JalviewColourChooser.showColourChooser(button, ttl, currentColor.getColour(), listener);
1672 * variable colour and filters dialog
1674 chooser = new FeatureTypeSettings(me.fr, type);
1675 chooser.setRequestFocusEnabled(true);
1676 chooser.requestFocus();
1677 chooser.addActionListener(this);
1678 chooser.showTab(true);
1680 // Make the renderer reappear.
1681 fireEditingStopped();
1686 * after OK in variable colour dialog, any changes to colour
1687 * (or filters!) are already set in FeatureRenderer, so just
1688 * update table data without triggering updateFeatureRenderer
1690 currentColor = fr.getFeatureColours().get(type);
1691 FeatureMatcherSetI currentFilter = me.fr.getFeatureFilter(type);
1692 if (currentFilter == null)
1694 currentFilter = new FeatureMatcherSet();
1696 Object[] data = ((FeatureTableModel) table.getModel())
1697 .getData()[rowSelected];
1698 data[COLOUR_COLUMN] = currentColor;
1699 data[FILTER_COLUMN] = currentFilter;
1701 fireEditingStopped();
1702 me.table.validate();
1706 // Implement the one CellEditor method that AbstractCellEditor doesn't.
1708 public Object getCellEditorValue()
1710 return currentColor;
1713 // Implement the one method defined by TableCellEditor.
1715 public Component getTableCellEditorComponent(JTable theTable, Object value,
1716 boolean isSelected, int row, int column)
1718 currentColor = (FeatureColourI) value;
1719 this.rowSelected = row;
1720 type = me.table.getValueAt(row, TYPE_COLUMN).toString();
1721 button.setOpaque(true);
1722 button.setBackground(me.getBackground());
1723 if (!currentColor.isSimpleColour())
1725 JLabel btn = new JLabel();
1726 btn.setSize(button.getSize());
1727 FeatureSettings.renderGraduatedColor(btn, currentColor);
1728 button.setBackground(btn.getBackground());
1729 button.setIcon(btn.getIcon());
1730 button.setText(btn.getText());
1735 button.setIcon(null);
1736 button.setBackground(currentColor.getColour());
1743 * The cell editor for the Filter column. It displays the text of any filters
1744 * for the feature type in that row (in full as a tooltip, possible abbreviated
1745 * as display text). On click in the cell, opens the Feature Display Settings
1746 * dialog at the Filters tab.
1748 class FilterEditor extends AbstractCellEditor
1749 implements TableCellEditor, ActionListener
1753 FeatureMatcherSetI currentFilter;
1761 protected static final String EDIT = "edit";
1763 int rowSelected = 0;
1765 public FilterEditor(FeatureSettings me)
1768 button = new JButton();
1769 button.setActionCommand(EDIT);
1770 button.addActionListener(this);
1771 button.setBorderPainted(false);
1775 * Handles events from the editor button
1778 public void actionPerformed(ActionEvent e)
1780 if (button == e.getSource())
1782 FeatureTypeSettings chooser = new FeatureTypeSettings(me.fr, type);
1783 chooser.addActionListener(this);
1784 chooser.setRequestFocusEnabled(true);
1785 chooser.requestFocus();
1786 if (lastLocation != null)
1788 // todo open at its last position on screen
1789 chooser.setBounds(lastLocation.x, lastLocation.y,
1790 chooser.getWidth(), chooser.getHeight());
1793 chooser.showTab(false);
1794 fireEditingStopped();
1796 else if (e.getSource() instanceof Component)
1800 * after OK in variable colour dialog, any changes to filter
1801 * (or colours!) are already set in FeatureRenderer, so just
1802 * update table data without triggering updateFeatureRenderer
1804 FeatureColourI currentColor = fr.getFeatureColours().get(type);
1805 currentFilter = me.fr.getFeatureFilter(type);
1806 if (currentFilter == null)
1808 currentFilter = new FeatureMatcherSet();
1810 Object[] data = ((FeatureTableModel) table.getModel())
1811 .getData()[rowSelected];
1812 data[COLOUR_COLUMN] = currentColor;
1813 data[FILTER_COLUMN] = currentFilter;
1814 fireEditingStopped();
1815 me.table.validate();
1820 public Object getCellEditorValue()
1822 return currentFilter;
1826 public Component getTableCellEditorComponent(JTable theTable, Object value,
1827 boolean isSelected, int row, int column)
1829 currentFilter = (FeatureMatcherSetI) value;
1830 this.rowSelected = row;
1831 type = me.table.getValueAt(row, TYPE_COLUMN).toString();
1832 button.setOpaque(true);
1833 button.setBackground(me.getBackground());
1834 button.setText(currentFilter.toString());
1835 button.setToolTipText(currentFilter.toString());
1836 button.setIcon(null);
1842 class FeatureIcon implements Icon
1844 FeatureColourI gcol;
1848 boolean midspace = false;
1850 int width = 50, height = 20;
1852 int s1, e1; // start and end of midpoint band for thresholded symbol
1854 Color mpcolour = Color.white;
1856 FeatureIcon(FeatureColourI gfc, Color bg, int w, int h, boolean mspace)
1876 public int getIconWidth()
1882 public int getIconHeight()
1888 public void paintIcon(Component c, Graphics g, int x, int y)
1891 if (gcol.isColourByLabel())
1894 g.fillRect(0, 0, width, height);
1895 // need an icon here.
1896 g.setColor(gcol.getMaxColour());
1898 g.setFont(new Font("Verdana", Font.PLAIN, 9));
1900 // g.setFont(g.getFont().deriveFont(
1901 // AffineTransform.getScaleInstance(
1902 // width/g.getFontMetrics().stringWidth("Label"),
1903 // height/g.getFontMetrics().getHeight())));
1905 g.drawString(MessageManager.getString("label.label"), 0, 0);
1910 Color minCol = gcol.getMinColour();
1912 g.fillRect(0, 0, s1, height);
1915 g.setColor(Color.white);
1916 g.fillRect(s1, 0, e1 - s1, height);
1918 g.setColor(gcol.getMaxColour());
1919 g.fillRect(0, e1, width - e1, height);