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.FeatureMatcher;
28 import jalview.datamodel.features.FeatureMatcherI;
29 import jalview.datamodel.features.FeatureMatcherSet;
30 import jalview.datamodel.features.FeatureMatcherSetI;
31 import jalview.gui.Help.HelpId;
32 import jalview.gui.JalviewColourChooser.ColourChooserListener;
33 import jalview.io.JalviewFileChooser;
34 import jalview.io.JalviewFileView;
35 import jalview.schemabinding.version2.Filter;
36 import jalview.schemabinding.version2.JalviewUserColours;
37 import jalview.schemabinding.version2.MatcherSet;
38 import jalview.schemes.FeatureColour;
39 import jalview.util.MessageManager;
40 import jalview.util.Platform;
41 import jalview.viewmodel.seqfeatures.FeatureRendererModel.FeatureSettingsBean;
43 import java.awt.BorderLayout;
44 import java.awt.Color;
45 import java.awt.Component;
46 import java.awt.Dimension;
48 import java.awt.Graphics;
49 import java.awt.GridLayout;
50 import java.awt.Point;
51 import java.awt.Rectangle;
52 import java.awt.event.ActionEvent;
53 import java.awt.event.ActionListener;
54 import java.awt.event.ItemEvent;
55 import java.awt.event.ItemListener;
56 import java.awt.event.MouseAdapter;
57 import java.awt.event.MouseEvent;
58 import java.awt.event.MouseMotionAdapter;
59 import java.beans.PropertyChangeEvent;
60 import java.beans.PropertyChangeListener;
62 import java.io.FileInputStream;
63 import java.io.FileOutputStream;
64 import java.io.InputStreamReader;
65 import java.io.OutputStreamWriter;
66 import java.io.PrintWriter;
67 import java.util.Arrays;
68 import java.util.Comparator;
69 import java.util.HashMap;
70 import java.util.HashSet;
71 import java.util.Hashtable;
72 import java.util.Iterator;
73 import java.util.List;
77 import javax.help.HelpSetException;
78 import javax.swing.AbstractCellEditor;
79 import javax.swing.BorderFactory;
80 import javax.swing.Icon;
81 import javax.swing.JButton;
82 import javax.swing.JCheckBox;
83 import javax.swing.JCheckBoxMenuItem;
84 import javax.swing.JColorChooser;
85 import javax.swing.JDialog;
86 import javax.swing.JInternalFrame;
87 import javax.swing.JLabel;
88 import javax.swing.JLayeredPane;
89 import javax.swing.JMenuItem;
90 import javax.swing.JPanel;
91 import javax.swing.JPopupMenu;
92 import javax.swing.JScrollPane;
93 import javax.swing.JSlider;
94 import javax.swing.JTable;
95 import javax.swing.ListSelectionModel;
96 import javax.swing.SwingConstants;
97 import javax.swing.ToolTipManager;
98 import javax.swing.border.Border;
99 import javax.swing.event.ChangeEvent;
100 import javax.swing.event.ChangeListener;
101 import javax.swing.table.AbstractTableModel;
102 import javax.swing.table.TableCellEditor;
103 import javax.swing.table.TableCellRenderer;
104 import javax.swing.table.TableColumn;
106 public class FeatureSettings extends JPanel
107 implements FeatureSettingsControllerI
109 private static final String SEQUENCE_FEATURE_COLOURS = MessageManager
110 .getString("label.sequence_feature_colours");
113 * column indices of fields in Feature Settings table
115 static final int TYPE_COLUMN = 0;
117 static final int COLOUR_COLUMN = 1;
119 static final int FILTER_COLUMN = 2;
121 static final int SHOW_COLUMN = 3;
123 private static final int COLUMN_COUNT = 4;
125 private static final int MIN_WIDTH = 400;
127 private static final int MIN_HEIGHT = 400;
129 private final static String BASE_TOOLTIP = "Click to edit, right-click for menu";
131 final FeatureRenderer fr;
133 public final AlignFrame af;
136 * 'original' fields hold settings to restore on Cancel
138 Object[][] originalData;
140 float originalTransparency;
142 Map<String, FeatureMatcherSetI> originalFilters;
144 final JInternalFrame frame;
146 JScrollPane scrollPane = new JScrollPane();
152 JSlider transparency = new JSlider();
155 * when true, constructor is still executing - so ignore UI events
157 protected volatile boolean inConstruction = true;
159 int selectedRow = -1;
161 JButton fetchDAS = new JButton();
163 JButton saveDAS = new JButton();
165 JButton cancelDAS = new JButton();
167 boolean resettingTable = false;
170 * true when Feature Settings are updating from feature renderer
172 boolean handlingUpdate = false;
175 * holds {featureCount, totalExtent} for each feature type
177 Map<String, float[]> typeWidth = null;
184 public FeatureSettings(AlignFrame alignFrame)
186 this.af = alignFrame;
187 fr = af.getFeatureRenderer();
189 // save transparency for restore on Cancel
190 originalTransparency = fr.getTransparency();
191 int originalTransparencyAsPercent = (int) (originalTransparency * 100);
192 transparency.setMaximum(100 - originalTransparencyAsPercent);
194 originalFilters = new HashMap<>(fr.getFeatureFilters()); // shallow copy
199 } catch (Exception ex)
201 ex.printStackTrace();
206 static final String tt = "Click to edit, right-click for menu"; // todo i18n
209 public String getToolTipText(MouseEvent e)
212 int column = table.columnAtPoint(e.getPoint());
213 int row = table.rowAtPoint(e.getPoint());
218 tip = JvSwingUtils.wrapTooltip(true, MessageManager
219 .getString("label.feature_settings_click_drag"));
222 FeatureColourI colour = (FeatureColourI) table.getValueAt(row,
224 tip = getColorTooltip(colour);
227 FeatureMatcherSet o = (FeatureMatcherSet) table.getValueAt(row,
230 ? MessageManager.getString("label.filters_tooltip")
240 * Position the tooltip near the bottom edge of, and half way across, the
244 public Point getToolTipLocation(MouseEvent e)
246 Point point = e.getPoint();
247 int column = table.columnAtPoint(point);
248 int row = table.rowAtPoint(point);
249 Rectangle r = getCellRect(row, column, false);
250 Point loc = new Point(r.x + r.width / 2, r.y + r.height - 3);
254 table.getTableHeader().setFont(new Font("Verdana", Font.PLAIN, 12));
255 table.setFont(new Font("Verdana", Font.PLAIN, 12));
256 ToolTipManager.sharedInstance().registerComponent(table);
258 // table.setDefaultRenderer(Color.class, new ColorRenderer());
259 // table.setDefaultEditor(Color.class, new ColorEditor(this));
261 table.setDefaultEditor(FeatureColour.class, new ColorEditor(this));
262 table.setDefaultRenderer(FeatureColour.class, new ColorRenderer());
264 table.setDefaultEditor(FeatureMatcherSet.class, new FilterEditor(this));
265 table.setDefaultRenderer(FeatureMatcherSet.class, new FilterRenderer());
267 TableColumn colourColumn = new TableColumn(COLOUR_COLUMN, 75,
268 new ColorRenderer(), new ColorEditor(this));
269 table.addColumn(colourColumn);
271 TableColumn filterColumn = new TableColumn(FILTER_COLUMN, 75,
272 new FilterRenderer(), new FilterEditor(this));
273 table.addColumn(filterColumn);
275 table.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
277 table.addMouseListener(new MouseAdapter()
280 public void mousePressed(MouseEvent evt)
282 selectedRow = table.rowAtPoint(evt.getPoint());
283 String type = (String) table.getValueAt(selectedRow, TYPE_COLUMN);
284 if (evt.isPopupTrigger())
286 Object colour = table.getValueAt(selectedRow, COLOUR_COLUMN);
287 popupSort(selectedRow, type, colour, fr.getMinMax(), evt.getX(),
290 else if (evt.getClickCount() == 2)
292 boolean invertSelection = evt.isAltDown();
293 boolean toggleSelection = Platform.isControlDown(evt);
294 boolean extendSelection = evt.isShiftDown();
295 fr.ap.alignFrame.avc.markColumnsContainingFeatures(
296 invertSelection, extendSelection, toggleSelection, type);
300 // isPopupTrigger fires on mouseReleased on Windows
302 public void mouseReleased(MouseEvent evt)
304 selectedRow = table.rowAtPoint(evt.getPoint());
305 if (evt.isPopupTrigger())
307 String type = (String) table.getValueAt(selectedRow, TYPE_COLUMN);
308 Object colour = table.getValueAt(selectedRow, COLOUR_COLUMN);
309 popupSort(selectedRow, type, colour, fr.getMinMax(), evt.getX(),
315 table.addMouseMotionListener(new MouseMotionAdapter()
318 public void mouseDragged(MouseEvent evt)
320 int newRow = table.rowAtPoint(evt.getPoint());
321 if (newRow != selectedRow && selectedRow != -1 && newRow != -1)
324 * reposition 'selectedRow' to 'newRow' (the dragged to location)
325 * this could be more than one row away for a very fast drag action
326 * so just swap it with adjacent rows until we get it there
328 Object[][] data = ((FeatureTableModel) table.getModel())
330 int direction = newRow < selectedRow ? -1 : 1;
331 for (int i = selectedRow; i != newRow; i += direction)
333 Object[] temp = data[i];
334 data[i] = data[i + direction];
335 data[i + direction] = temp;
337 updateFeatureRenderer(data);
339 selectedRow = newRow;
343 // table.setToolTipText(JvSwingUtils.wrapTooltip(true,
344 // MessageManager.getString("label.feature_settings_click_drag")));
345 scrollPane.setViewportView(table);
347 if (af.getViewport().isShowSequenceFeatures() || !fr.hasRenderOrder())
349 fr.findAllFeatures(true); // display everything!
352 discoverAllFeatureData();
353 final PropertyChangeListener change;
354 final FeatureSettings fs = this;
355 fr.addPropertyChangeListener(change = new PropertyChangeListener()
358 public void propertyChange(PropertyChangeEvent evt)
360 if (!fs.resettingTable && !fs.handlingUpdate)
362 fs.handlingUpdate = true;
364 // new groups may be added with new sequence feature types only
365 fs.handlingUpdate = false;
371 frame = new JInternalFrame();
372 frame.setContentPane(this);
373 if (Platform.isAMac())
375 Desktop.addInternalFrame(frame,
376 MessageManager.getString("label.sequence_feature_settings"),
381 Desktop.addInternalFrame(frame,
382 MessageManager.getString("label.sequence_feature_settings"),
385 frame.setMinimumSize(new Dimension(MIN_WIDTH, MIN_HEIGHT));
387 frame.addInternalFrameListener(
388 new javax.swing.event.InternalFrameAdapter()
391 public void internalFrameClosed(
392 javax.swing.event.InternalFrameEvent evt)
394 fr.removePropertyChangeListener(change);
397 frame.setLayer(JLayeredPane.PALETTE_LAYER);
398 inConstruction = false;
401 protected void popupSort(final int rowSelected, final String type,
402 final Object typeCol, final Map<String, float[][]> minmax, int x,
405 final FeatureColourI featureColour = (FeatureColourI) typeCol;
407 JPopupMenu men = new JPopupMenu(MessageManager
408 .formatMessage("label.settings_for_param", new String[]
410 JMenuItem scr = new JMenuItem(
411 MessageManager.getString("label.sort_by_score"));
413 final FeatureSettings me = this;
414 scr.addActionListener(new ActionListener()
418 public void actionPerformed(ActionEvent e)
421 .sortAlignmentByFeatureScore(Arrays.asList(new String[]
426 JMenuItem dens = new JMenuItem(
427 MessageManager.getString("label.sort_by_density"));
428 dens.addActionListener(new ActionListener()
432 public void actionPerformed(ActionEvent e)
435 .sortAlignmentByFeatureDensity(Arrays.asList(new String[]
443 * variable colour options include colour by label, by score,
444 * by selected attribute text, or attribute value
446 final JCheckBoxMenuItem variableColourCB = new JCheckBoxMenuItem(
447 MessageManager.getString("label.variable_colour"));
448 variableColourCB.setSelected(!featureColour.isSimpleColour());
449 men.add(variableColourCB);
452 * checkbox action listener doubles up as listener to OK
453 * from the variable colour / filters dialog
455 variableColourCB.addActionListener(new ActionListener()
458 public void actionPerformed(ActionEvent e)
460 if (e.getSource() == variableColourCB)
462 if (featureColour.isSimpleColour())
465 * toggle simple colour to variable colour - show dialog
467 FeatureTypeSettings fc = new FeatureTypeSettings(me.fr, type);
468 fc.addActionListener(this);
473 * toggle variable to simple colour - show colour chooser
475 String title = MessageManager.getString("label.select_colour");
476 ColourChooserListener listener = new ColourChooserListener()
479 public void colourSelected(Color c)
481 table.setValueAt(new FeatureColour(c), rowSelected,
484 me.updateFeatureRenderer(
485 ((FeatureTableModel) table.getModel()).getData(),
489 JalviewColourChooser.showColourChooser(me, title, featureColour.getMaxColour(), listener);
493 if (e.getSource() instanceof FeatureTypeSettings)
496 * update after OK in feature colour dialog; the updated
497 * colour will have already been set in the FeatureRenderer
499 FeatureColourI fci = fr.getFeatureColours().get(type);
500 table.setValueAt(fci, rowSelected, COLOUR_COLUMN);
508 JMenuItem selCols = new JMenuItem(
509 MessageManager.getString("label.select_columns_containing"));
510 selCols.addActionListener(new ActionListener()
513 public void actionPerformed(ActionEvent arg0)
515 fr.ap.alignFrame.avc.markColumnsContainingFeatures(false, false,
519 JMenuItem clearCols = new JMenuItem(MessageManager
520 .getString("label.select_columns_not_containing"));
521 clearCols.addActionListener(new ActionListener()
524 public void actionPerformed(ActionEvent arg0)
526 fr.ap.alignFrame.avc.markColumnsContainingFeatures(true, false,
530 JMenuItem hideCols = new JMenuItem(
531 MessageManager.getString("label.hide_columns_containing"));
532 hideCols.addActionListener(new ActionListener()
535 public void actionPerformed(ActionEvent arg0)
537 fr.ap.alignFrame.hideFeatureColumns(type, true);
540 JMenuItem hideOtherCols = new JMenuItem(
541 MessageManager.getString("label.hide_columns_not_containing"));
542 hideOtherCols.addActionListener(new ActionListener()
545 public void actionPerformed(ActionEvent arg0)
547 fr.ap.alignFrame.hideFeatureColumns(type, false);
553 men.add(hideOtherCols);
554 men.show(table, x, y);
558 synchronized public void discoverAllFeatureData()
560 Set<String> allGroups = new HashSet<>();
561 AlignmentI alignment = af.getViewport().getAlignment();
563 for (int i = 0; i < alignment.getHeight(); i++)
565 SequenceI seq = alignment.getSequenceAt(i);
566 for (String group : seq.getFeatures().getFeatureGroups(true))
568 if (group != null && !allGroups.contains(group))
570 allGroups.add(group);
571 checkGroupState(group);
582 * Synchronise gui group list and check visibility of group
585 * @return true if group is visible
587 private boolean checkGroupState(String group)
589 boolean visible = fr.checkGroupVisibility(group, true);
591 for (int g = 0; g < groupPanel.getComponentCount(); g++)
593 if (((JCheckBox) groupPanel.getComponent(g)).getText().equals(group))
595 ((JCheckBox) groupPanel.getComponent(g)).setSelected(visible);
600 final String grp = group;
601 final JCheckBox check = new JCheckBox(group, visible);
602 check.setFont(new Font("Serif", Font.BOLD, 12));
603 check.setToolTipText(group);
604 check.addItemListener(new ItemListener()
607 public void itemStateChanged(ItemEvent evt)
609 fr.setGroupVisibility(check.getText(), check.isSelected());
610 resetTable(new String[] { grp });
611 af.alignPanel.paintAlignment(true, true);
614 groupPanel.add(check);
618 synchronized void resetTable(String[] groupChanged)
624 resettingTable = true;
625 typeWidth = new Hashtable<>();
626 // TODO: change avWidth calculation to 'per-sequence' average and use long
629 Set<String> displayableTypes = new HashSet<>();
630 Set<String> foundGroups = new HashSet<>();
633 * determine which feature types may be visible depending on
634 * which groups are selected, and recompute average width data
636 for (int i = 0; i < af.getViewport().getAlignment().getHeight(); i++)
639 SequenceI seq = af.getViewport().getAlignment().getSequenceAt(i);
642 * get the sequence's groups for positional features
643 * and keep track of which groups are visible
645 Set<String> groups = seq.getFeatures().getFeatureGroups(true);
646 Set<String> visibleGroups = new HashSet<>();
647 for (String group : groups)
649 if (group == null || checkGroupState(group))
651 visibleGroups.add(group);
654 foundGroups.addAll(groups);
657 * get distinct feature types for visible groups
658 * record distinct visible types, and their count and total length
660 Set<String> types = seq.getFeatures().getFeatureTypesForGroups(true,
661 visibleGroups.toArray(new String[visibleGroups.size()]));
662 for (String type : types)
664 displayableTypes.add(type);
665 float[] avWidth = typeWidth.get(type);
668 avWidth = new float[2];
669 typeWidth.put(type, avWidth);
671 // todo this could include features with a non-visible group
672 // - do we greatly care?
673 // todo should we include non-displayable features here, and only
674 // update when features are added?
675 avWidth[0] += seq.getFeatures().getFeatureCount(true, type);
676 avWidth[1] += seq.getFeatures().getTotalFeatureLength(type);
680 Object[][] data = new Object[displayableTypes.size()][COLUMN_COUNT];
683 if (fr.hasRenderOrder())
687 fr.findAllFeatures(groupChanged != null); // prod to update
688 // colourschemes. but don't
690 // First add the checks in the previous render order,
691 // in case the window has been closed and reopened
693 List<String> frl = fr.getRenderOrder();
694 for (int ro = frl.size() - 1; ro > -1; ro--)
696 String type = frl.get(ro);
698 if (!displayableTypes.contains(type))
703 data[dataIndex][TYPE_COLUMN] = type;
704 data[dataIndex][COLOUR_COLUMN] = fr.getFeatureStyle(type);
705 FeatureMatcherSetI featureFilter = fr.getFeatureFilter(type);
706 data[dataIndex][FILTER_COLUMN] = featureFilter == null
707 ? new FeatureMatcherSet()
709 data[dataIndex][SHOW_COLUMN] = new Boolean(
710 af.getViewport().getFeaturesDisplayed().isVisible(type));
712 displayableTypes.remove(type);
717 * process any extra features belonging only to
718 * a group which was just selected
720 while (!displayableTypes.isEmpty())
722 String type = displayableTypes.iterator().next();
723 data[dataIndex][TYPE_COLUMN] = type;
725 data[dataIndex][COLOUR_COLUMN] = fr.getFeatureStyle(type);
726 if (data[dataIndex][COLOUR_COLUMN] == null)
728 // "Colour has been updated in another view!!"
729 fr.clearRenderOrder();
732 FeatureMatcherSetI featureFilter = fr.getFeatureFilter(type);
733 data[dataIndex][FILTER_COLUMN] = featureFilter == null
734 ? new FeatureMatcherSet()
736 data[dataIndex][SHOW_COLUMN] = new Boolean(true);
738 displayableTypes.remove(type);
741 if (originalData == null)
743 originalData = new Object[data.length][COLUMN_COUNT];
744 for (int i = 0; i < data.length; i++)
746 System.arraycopy(data[i], 0, originalData[i], 0, COLUMN_COUNT);
751 updateOriginalData(data);
754 table.setModel(new FeatureTableModel(data));
755 table.getColumnModel().getColumn(0).setPreferredWidth(200);
757 groupPanel.setLayout(
758 new GridLayout(fr.getFeatureGroupsSize() / 4 + 1, 4));
759 pruneGroups(foundGroups);
760 groupPanel.validate();
762 updateFeatureRenderer(data, groupChanged != null);
763 resettingTable = false;
767 * Updates 'originalData' (used for restore on Cancel) if we detect that changes
768 * have been made outwith this dialog
770 * <li>a new feature type added (and made visible)</li>
771 * <li>a feature colour changed (in the Amend Features dialog)</li>
776 protected void updateOriginalData(Object[][] foundData)
778 // todo LinkedHashMap instead of Object[][] would be nice
780 Object[][] currentData = ((FeatureTableModel) table.getModel())
782 for (Object[] row : foundData)
784 String type = (String) row[TYPE_COLUMN];
785 boolean found = false;
786 for (Object[] current : currentData)
788 if (type.equals(current[TYPE_COLUMN]))
792 * currently dependent on object equality here;
793 * really need an equals method on FeatureColour
795 if (!row[COLOUR_COLUMN].equals(current[COLOUR_COLUMN]))
798 * feature colour has changed externally - update originalData
800 for (Object[] original : originalData)
802 if (type.equals(original[TYPE_COLUMN]))
804 original[COLOUR_COLUMN] = row[COLOUR_COLUMN];
815 * new feature detected - add to original data (on top)
817 Object[][] newData = new Object[originalData.length
819 for (int i = 0; i < originalData.length; i++)
821 System.arraycopy(originalData[i], 0, newData[i + 1], 0,
825 originalData = newData;
831 * Remove from the groups panel any checkboxes for groups that are not in the
832 * foundGroups set. This enables removing a group from the display when the last
833 * feature in that group is deleted.
837 protected void pruneGroups(Set<String> foundGroups)
839 for (int g = 0; g < groupPanel.getComponentCount(); g++)
841 JCheckBox checkbox = (JCheckBox) groupPanel.getComponent(g);
842 if (!foundGroups.contains(checkbox.getText()))
844 groupPanel.remove(checkbox);
850 * reorder data based on the featureRenderers global priority list.
854 private void ensureOrder(Object[][] data)
856 boolean sort = false;
857 float[] order = new float[data.length];
858 for (int i = 0; i < order.length; i++)
860 order[i] = fr.getOrder(data[i][0].toString());
863 order[i] = fr.setOrder(data[i][0].toString(), i / order.length);
867 sort = sort || order[i - 1] > order[i];
872 jalview.util.QuickSort.sort(order, data);
877 * Offers a file chooser dialog, and then loads the feature colours and
878 * filters from file in XML format and unmarshals to Jalview feature settings
882 // TODO: JAL-3048 relies on Castor XML parsing: not needed for JS-jalview core
885 JalviewFileChooser chooser = new JalviewFileChooser("fc",
886 SEQUENCE_FEATURE_COLOURS);
887 chooser.setFileView(new JalviewFileView());
888 chooser.setDialogTitle(
889 MessageManager.getString("label.load_feature_colours"));
890 chooser.setToolTipText(MessageManager.getString("action.load"));
892 int value = chooser.showOpenDialog(this);
894 if (value == JalviewFileChooser.APPROVE_OPTION)
896 File file = chooser.getSelectedFile();
902 * Loads feature colours and filters from XML stored in the given file
910 InputStreamReader in = new InputStreamReader(
911 new FileInputStream(file), "UTF-8");
913 JalviewUserColours jucs = JalviewUserColours.unmarshal(in);
916 * load feature colours
918 for (int i = jucs.getColourCount() - 1; i >= 0; i--)
920 jalview.schemabinding.version2.Colour newcol = jucs.getColour(i);
921 FeatureColourI colour = Jalview2XML.unmarshalColour(newcol);
922 fr.setColour(newcol.getName(), colour);
923 fr.setOrder(newcol.getName(), i / (float) jucs.getColourCount());
927 * load feature filters; loaded filters will replace any that are
928 * currently defined, other defined filters are left unchanged
930 for (int i = 0; i < jucs.getFilterCount(); i++)
932 jalview.schemabinding.version2.Filter filterModel = jucs
934 String featureType = filterModel.getFeatureType();
935 FeatureMatcherSetI filter = Jalview2XML.unmarshalFilter(featureType,
936 filterModel.getMatcherSet());
937 if (!filter.isEmpty())
939 fr.setFeatureFilter(featureType, filter);
944 * update feature settings table
949 Object[][] data = ((FeatureTableModel) table.getModel())
952 updateFeatureRenderer(data, false);
955 } catch (Exception ex)
957 System.out.println("Error loading User Colour File\n" + ex);
962 * Offers a file chooser dialog, and then saves the current feature colours
963 * and any filters to the selected file in XML format
967 // TODO: JAL-3048 not needed for Jalview-JS - save colours
968 JalviewFileChooser chooser = new JalviewFileChooser("fc",
969 SEQUENCE_FEATURE_COLOURS);
970 chooser.setFileView(new JalviewFileView());
971 chooser.setDialogTitle(
972 MessageManager.getString("label.save_feature_colours"));
973 chooser.setToolTipText(MessageManager.getString("action.save"));
975 int value = chooser.showSaveDialog(this);
977 if (value == JalviewFileChooser.APPROVE_OPTION)
979 save(chooser.getSelectedFile());
984 * Saves feature colours and filters to the given file
990 JalviewUserColours ucs = new JalviewUserColours();
991 ucs.setSchemeName("Sequence Features");
994 PrintWriter out = new PrintWriter(new OutputStreamWriter(
995 new FileOutputStream(file), "UTF-8"));
998 * sort feature types by colour order, from 0 (highest)
1001 Set<String> fr_colours = fr.getAllFeatureColours();
1002 String[] sortedTypes = fr_colours
1003 .toArray(new String[fr_colours.size()]);
1004 Arrays.sort(sortedTypes, new Comparator<String>()
1007 public int compare(String type1, String type2)
1009 return Float.compare(fr.getOrder(type1), fr.getOrder(type2));
1014 * save feature colours
1016 for (String featureType : sortedTypes)
1018 FeatureColourI fcol = fr.getFeatureStyle(featureType);
1019 jalview.schemabinding.version2.Colour col = Jalview2XML.marshalColour(
1025 * save any feature filters
1027 for (String featureType : sortedTypes)
1029 FeatureMatcherSetI filter = fr.getFeatureFilter(featureType);
1030 if (filter != null && !filter.isEmpty())
1032 Iterator<FeatureMatcherI> iterator = filter.getMatchers().iterator();
1033 FeatureMatcherI firstMatcher = iterator.next();
1034 MatcherSet ms = Jalview2XML.marshalFilter(firstMatcher, iterator,
1036 Filter filterModel = new Filter();
1037 filterModel.setFeatureType(featureType);
1038 filterModel.setMatcherSet(ms);
1039 ucs.addFilter(filterModel);
1045 } catch (Exception ex)
1047 ex.printStackTrace();
1051 public void invertSelection()
1053 Object[][] data = ((FeatureTableModel) table.getModel()).getData();
1054 for (int i = 0; i < data.length; i++)
1056 data[i][SHOW_COLUMN] = !(Boolean) data[i][SHOW_COLUMN];
1058 updateFeatureRenderer(data, true);
1062 public void orderByAvWidth()
1064 if (table == null || table.getModel() == null)
1068 Object[][] data = ((FeatureTableModel) table.getModel()).getData();
1069 float[] width = new float[data.length];
1073 for (int i = 0; i < data.length; i++)
1075 awidth = typeWidth.get(data[i][TYPE_COLUMN]);
1078 width[i] = awidth[1] / awidth[0];// *awidth[0]*awidth[2]; - better
1079 // weight - but have to make per
1080 // sequence, too (awidth[2])
1081 // if (width[i]==1) // hack to distinguish single width sequences.
1092 boolean sort = false;
1093 for (int i = 0; i < width.length; i++)
1095 // awidth = (float[]) typeWidth.get(data[i][0]);
1098 width[i] = fr.getOrder(data[i][TYPE_COLUMN].toString());
1101 width[i] = fr.setOrder(data[i][TYPE_COLUMN].toString(),
1107 width[i] /= max; // normalize
1108 fr.setOrder(data[i][TYPE_COLUMN].toString(), width[i]); // store for later
1112 sort = sort || width[i - 1] > width[i];
1117 jalview.util.QuickSort.sort(width, data);
1118 // update global priority order
1121 updateFeatureRenderer(data, false);
1129 frame.setClosed(true);
1130 } catch (Exception exe)
1136 public void updateFeatureRenderer(Object[][] data)
1138 updateFeatureRenderer(data, true);
1142 * Update the priority order of features; only repaint if this changed the order
1143 * of visible features
1148 void updateFeatureRenderer(Object[][] data, boolean visibleNew)
1150 FeatureSettingsBean[] rowData = getTableAsBeans(data);
1152 if (fr.setFeaturePriority(rowData, visibleNew))
1154 af.alignPanel.paintAlignment(true, true);
1159 * Converts table data into an array of data beans
1161 private FeatureSettingsBean[] getTableAsBeans(Object[][] data)
1163 FeatureSettingsBean[] rowData = new FeatureSettingsBean[data.length];
1164 for (int i = 0; i < data.length; i++)
1166 String type = (String) data[i][TYPE_COLUMN];
1167 FeatureColourI colour = (FeatureColourI) data[i][COLOUR_COLUMN];
1168 FeatureMatcherSetI theFilter = (FeatureMatcherSetI) data[i][FILTER_COLUMN];
1169 Boolean isShown = (Boolean) data[i][SHOW_COLUMN];
1170 rowData[i] = new FeatureSettingsBean(type, colour, theFilter,
1176 private void jbInit() throws Exception
1178 this.setLayout(new BorderLayout());
1180 JPanel settingsPane = new JPanel();
1181 settingsPane.setLayout(new BorderLayout());
1183 JPanel bigPanel = new JPanel();
1184 bigPanel.setLayout(new BorderLayout());
1186 groupPanel = new JPanel();
1187 bigPanel.add(groupPanel, BorderLayout.NORTH);
1189 JButton invert = new JButton(
1190 MessageManager.getString("label.invert_selection"));
1191 invert.setFont(JvSwingUtils.getLabelFont());
1192 invert.addActionListener(new ActionListener()
1195 public void actionPerformed(ActionEvent e)
1201 JButton optimizeOrder = new JButton(
1202 MessageManager.getString("label.optimise_order"));
1203 optimizeOrder.setFont(JvSwingUtils.getLabelFont());
1204 optimizeOrder.addActionListener(new ActionListener()
1207 public void actionPerformed(ActionEvent e)
1213 JButton sortByScore = new JButton(
1214 MessageManager.getString("label.seq_sort_by_score"));
1215 sortByScore.setFont(JvSwingUtils.getLabelFont());
1216 sortByScore.addActionListener(new ActionListener()
1219 public void actionPerformed(ActionEvent e)
1221 af.avc.sortAlignmentByFeatureScore(null);
1224 JButton sortByDens = new JButton(
1225 MessageManager.getString("label.sequence_sort_by_density"));
1226 sortByDens.setFont(JvSwingUtils.getLabelFont());
1227 sortByDens.addActionListener(new ActionListener()
1230 public void actionPerformed(ActionEvent e)
1232 af.avc.sortAlignmentByFeatureDensity(null);
1236 JButton help = new JButton(MessageManager.getString("action.help"));
1237 help.setFont(JvSwingUtils.getLabelFont());
1238 help.addActionListener(new ActionListener()
1241 public void actionPerformed(ActionEvent e)
1245 Help.showHelpWindow(HelpId.SequenceFeatureSettings);
1246 } catch (HelpSetException e1)
1248 e1.printStackTrace();
1252 help.setFont(JvSwingUtils.getLabelFont());
1253 help.setText(MessageManager.getString("action.help"));
1254 help.addActionListener(new ActionListener()
1257 public void actionPerformed(ActionEvent e)
1261 Help.showHelpWindow(HelpId.SequenceFeatureSettings);
1262 } catch (HelpSetException e1)
1264 e1.printStackTrace();
1269 JButton cancel = new JButton(MessageManager.getString("action.cancel"));
1270 cancel.setFont(JvSwingUtils.getLabelFont());
1271 cancel.addActionListener(new ActionListener()
1274 public void actionPerformed(ActionEvent e)
1276 fr.setTransparency(originalTransparency);
1277 fr.setFeatureFilters(originalFilters);
1278 updateFeatureRenderer(originalData);
1283 JButton ok = new JButton(MessageManager.getString("action.ok"));
1284 ok.setFont(JvSwingUtils.getLabelFont());
1285 ok.addActionListener(new ActionListener()
1288 public void actionPerformed(ActionEvent e)
1294 JButton loadColours = new JButton(
1295 MessageManager.getString("label.load_colours"));
1296 loadColours.setFont(JvSwingUtils.getLabelFont());
1297 loadColours.setToolTipText(
1298 MessageManager.getString("label.load_colours_tooltip"));
1299 loadColours.addActionListener(new ActionListener()
1302 public void actionPerformed(ActionEvent e)
1308 JButton saveColours = new JButton(
1309 MessageManager.getString("label.save_colours"));
1310 saveColours.setFont(JvSwingUtils.getLabelFont());
1311 saveColours.setToolTipText(
1312 MessageManager.getString("label.save_colours_tooltip"));
1313 saveColours.addActionListener(new ActionListener()
1316 public void actionPerformed(ActionEvent e)
1321 transparency.addChangeListener(new ChangeListener()
1324 public void stateChanged(ChangeEvent evt)
1326 if (!inConstruction)
1328 fr.setTransparency((100 - transparency.getValue()) / 100f);
1329 af.alignPanel.paintAlignment(true, true);
1334 transparency.setMaximum(70);
1335 transparency.setToolTipText(
1336 MessageManager.getString("label.transparency_tip"));
1338 JPanel transPanel = new JPanel(new GridLayout(1, 2));
1339 bigPanel.add(transPanel, BorderLayout.SOUTH);
1341 JPanel transbuttons = new JPanel(new GridLayout(5, 1));
1342 transbuttons.add(optimizeOrder);
1343 transbuttons.add(invert);
1344 transbuttons.add(sortByScore);
1345 transbuttons.add(sortByDens);
1346 transbuttons.add(help);
1347 transPanel.add(transparency);
1348 transPanel.add(transbuttons);
1350 JPanel buttonPanel = new JPanel();
1351 buttonPanel.add(ok);
1352 buttonPanel.add(cancel);
1353 buttonPanel.add(loadColours);
1354 buttonPanel.add(saveColours);
1355 bigPanel.add(scrollPane, BorderLayout.CENTER);
1356 settingsPane.add(bigPanel, BorderLayout.CENTER);
1357 settingsPane.add(buttonPanel, BorderLayout.SOUTH);
1358 this.add(settingsPane);
1362 * Answers a suitable tooltip to show on the colour cell of the table
1367 public static String getColorTooltip(FeatureColourI fcol)
1373 if (fcol.isSimpleColour())
1375 return BASE_TOOLTIP;
1377 String description = fcol.getDescription();
1378 description = description.replaceAll("<", "<");
1379 description = description.replaceAll(">", ">");
1380 StringBuilder tt = new StringBuilder(description);
1381 tt.append("<br>").append(BASE_TOOLTIP).append("</br>");
1382 return JvSwingUtils.wrapTooltip(true, tt.toString());
1385 public static void renderGraduatedColor(JLabel comp, FeatureColourI gcol,
1388 boolean thr = false;
1389 StringBuilder tx = new StringBuilder();
1391 if (gcol.isColourByAttribute())
1393 tx.append(FeatureMatcher
1394 .toAttributeDisplayName(gcol.getAttributeName()));
1396 else if (!gcol.isColourByLabel())
1398 tx.append(MessageManager.getString("label.score"));
1401 if (gcol.isAboveThreshold())
1406 if (gcol.isBelowThreshold())
1411 if (gcol.isColourByLabel())
1417 if (!gcol.isColourByAttribute())
1425 Color newColor = gcol.getMaxColour();
1426 comp.setBackground(newColor);
1427 // System.err.println("Width is " + w / 2);
1428 Icon ficon = new FeatureIcon(gcol, comp.getBackground(), w, h, thr);
1429 comp.setIcon(ficon);
1430 // tt+="RGB value: Max (" + newColor.getRed() + ", "
1431 // + newColor.getGreen() + ", " + newColor.getBlue()
1432 // + ")\nMin (" + minCol.getRed() + ", " + minCol.getGreen()
1433 // + ", " + minCol.getBlue() + ")");
1435 comp.setHorizontalAlignment(SwingConstants.CENTER);
1436 comp.setText(tx.toString());
1439 // ///////////////////////////////////////////////////////////////////////
1440 // http://java.sun.com/docs/books/tutorial/uiswing/components/table.html
1441 // ///////////////////////////////////////////////////////////////////////
1442 class FeatureTableModel extends AbstractTableModel
1444 private String[] columnNames = {
1445 MessageManager.getString("label.feature_type"),
1446 MessageManager.getString("action.colour"),
1447 MessageManager.getString("label.filter"),
1448 MessageManager.getString("label.show") };
1450 private Object[][] data;
1452 FeatureTableModel(Object[][] data)
1457 public Object[][] getData()
1462 public void setData(Object[][] data)
1468 public int getColumnCount()
1470 return columnNames.length;
1473 public Object[] getRow(int row)
1479 public int getRowCount()
1485 public String getColumnName(int col)
1487 return columnNames[col];
1491 public Object getValueAt(int row, int col)
1493 return data[row][col];
1497 * Answers the class of the object in column c of the first row of the table
1500 public Class<?> getColumnClass(int c)
1502 Object v = getValueAt(0, c);
1503 return v == null ? null : v.getClass();
1507 public boolean isCellEditable(int row, int col)
1509 return col == 0 ? false : true;
1513 public void setValueAt(Object value, int row, int col)
1515 data[row][col] = value;
1516 fireTableCellUpdated(row, col);
1517 updateFeatureRenderer(data);
1522 class ColorRenderer extends JLabel implements TableCellRenderer
1524 Border unselectedBorder = null;
1526 Border selectedBorder = null;
1528 public ColorRenderer()
1530 setOpaque(true); // MUST do this for background to show up.
1531 setHorizontalTextPosition(SwingConstants.CENTER);
1532 setVerticalTextPosition(SwingConstants.CENTER);
1536 public Component getTableCellRendererComponent(JTable tbl, Object color,
1537 boolean isSelected, boolean hasFocus, int row, int column)
1539 FeatureColourI cellColour = (FeatureColourI) color;
1541 setBackground(tbl.getBackground());
1542 if (!cellColour.isSimpleColour())
1544 Rectangle cr = tbl.getCellRect(row, column, false);
1545 FeatureSettings.renderGraduatedColor(this, cellColour,
1546 (int) cr.getWidth(), (int) cr.getHeight());
1552 setBackground(cellColour.getColour());
1556 if (selectedBorder == null)
1558 selectedBorder = BorderFactory.createMatteBorder(2, 5, 2, 5,
1559 tbl.getSelectionBackground());
1561 setBorder(selectedBorder);
1565 if (unselectedBorder == null)
1567 unselectedBorder = BorderFactory.createMatteBorder(2, 5, 2, 5,
1568 tbl.getBackground());
1570 setBorder(unselectedBorder);
1577 class FilterRenderer extends JLabel implements TableCellRenderer
1579 javax.swing.border.Border unselectedBorder = null;
1581 javax.swing.border.Border selectedBorder = null;
1583 public FilterRenderer()
1585 setOpaque(true); // MUST do this for background to show up.
1586 setHorizontalTextPosition(SwingConstants.CENTER);
1587 setVerticalTextPosition(SwingConstants.CENTER);
1591 public Component getTableCellRendererComponent(JTable tbl,
1592 Object filter, boolean isSelected, boolean hasFocus, int row,
1595 FeatureMatcherSetI theFilter = (FeatureMatcherSetI) filter;
1597 String asText = theFilter.toString();
1598 setBackground(tbl.getBackground());
1599 this.setText(asText);
1604 if (selectedBorder == null)
1606 selectedBorder = BorderFactory.createMatteBorder(2, 5, 2, 5,
1607 tbl.getSelectionBackground());
1609 setBorder(selectedBorder);
1613 if (unselectedBorder == null)
1615 unselectedBorder = BorderFactory.createMatteBorder(2, 5, 2, 5,
1616 tbl.getBackground());
1618 setBorder(unselectedBorder);
1626 * update comp using rendering settings from gcol
1631 public static void renderGraduatedColor(JLabel comp, FeatureColourI gcol)
1633 int w = comp.getWidth(), h = comp.getHeight();
1636 w = (int) comp.getPreferredSize().getWidth();
1637 h = (int) comp.getPreferredSize().getHeight();
1644 renderGraduatedColor(comp, gcol, w, h);
1647 class ColorEditor extends AbstractCellEditor
1648 implements TableCellEditor, ActionListener
1652 FeatureColourI currentColor;
1654 FeatureTypeSettings chooser;
1660 protected static final String EDIT = "edit";
1662 int rowSelected = 0;
1664 public ColorEditor(FeatureSettings fs)
1667 // Set up the editor (from the table's point of view),
1668 // which is a button.
1669 // This button brings up the color chooser dialog,
1670 // which is the editor from the user's point of view.
1671 button = new JButton();
1672 button.setActionCommand(EDIT);
1673 button.addActionListener(this);
1674 button.setBorderPainted(false);
1678 * Handles events from the editor button, and from the colour/filters
1679 * dialog's OK button
1682 public void actionPerformed(ActionEvent e)
1684 if (button == e.getSource())
1686 if (currentColor.isSimpleColour())
1689 * simple colour chooser
1691 String ttl = MessageManager.getString("label.select_colour");
1692 ColourChooserListener listener = new ColourChooserListener() {
1694 public void colourSelected(Color c)
1696 currentColor = new FeatureColour(c);
1697 me.table.setValueAt(currentColor, rowSelected, COLOUR_COLUMN);
1700 JalviewColourChooser.showColourChooser(button, ttl, currentColor.getColour(), listener);
1705 * variable colour and filters dialog
1707 chooser = new FeatureTypeSettings(me.fr, type);
1712 chooser.setRequestFocusEnabled(true);
1713 chooser.requestFocus();
1715 chooser.addActionListener(this);
1716 // Make the renderer reappear.
1717 fireEditingStopped();
1723 * after OK in variable colour dialog, any changes to colour
1724 * (or filters!) are already set in FeatureRenderer, so just
1725 * update table data without triggering updateFeatureRenderer
1727 currentColor = fr.getFeatureColours().get(type);
1728 FeatureMatcherSetI currentFilter = me.fr.getFeatureFilter(type);
1729 if (currentFilter == null)
1731 currentFilter = new FeatureMatcherSet();
1733 Object[] data = ((FeatureTableModel) table.getModel())
1734 .getData()[rowSelected];
1735 data[COLOUR_COLUMN] = currentColor;
1736 data[FILTER_COLUMN] = currentFilter;
1738 fireEditingStopped();
1739 me.table.validate();
1743 // Implement the one CellEditor method that AbstractCellEditor doesn't.
1745 public Object getCellEditorValue()
1747 return currentColor;
1750 // Implement the one method defined by TableCellEditor.
1752 public Component getTableCellEditorComponent(JTable theTable, Object value,
1753 boolean isSelected, int row, int column)
1755 currentColor = (FeatureColourI) value;
1756 this.rowSelected = row;
1757 type = me.table.getValueAt(row, TYPE_COLUMN).toString();
1758 button.setOpaque(true);
1759 button.setBackground(me.getBackground());
1760 if (!currentColor.isSimpleColour())
1762 JLabel btn = new JLabel();
1763 btn.setSize(button.getSize());
1764 FeatureSettings.renderGraduatedColor(btn, currentColor);
1765 button.setBackground(btn.getBackground());
1766 button.setIcon(btn.getIcon());
1767 button.setText(btn.getText());
1772 button.setIcon(null);
1773 button.setBackground(currentColor.getColour());
1780 * The cell editor for the Filter column. It displays the text of any filters
1781 * for the feature type in that row (in full as a tooltip, possible abbreviated
1782 * as display text). On click in the cell, opens the Feature Display Settings
1783 * dialog at the Filters tab.
1785 class FilterEditor extends AbstractCellEditor
1786 implements TableCellEditor, ActionListener
1790 FeatureMatcherSetI currentFilter;
1798 protected static final String EDIT = "edit";
1800 int rowSelected = 0;
1802 public FilterEditor(FeatureSettings me)
1805 button = new JButton();
1806 button.setActionCommand(EDIT);
1807 button.addActionListener(this);
1808 button.setBorderPainted(false);
1812 * Handles events from the editor button
1815 public void actionPerformed(ActionEvent e)
1817 if (button == e.getSource())
1819 FeatureTypeSettings chooser = new FeatureTypeSettings(me.fr, type);
1820 chooser.addActionListener(this);
1821 chooser.setRequestFocusEnabled(true);
1822 chooser.requestFocus();
1823 if (lastLocation != null)
1825 // todo open at its last position on screen
1826 chooser.setBounds(lastLocation.x, lastLocation.y,
1827 chooser.getWidth(), chooser.getHeight());
1830 fireEditingStopped();
1832 else if (e.getSource() instanceof Component)
1836 * after OK in variable colour dialog, any changes to filter
1837 * (or colours!) are already set in FeatureRenderer, so just
1838 * update table data without triggering updateFeatureRenderer
1840 FeatureColourI currentColor = fr.getFeatureColours().get(type);
1841 currentFilter = me.fr.getFeatureFilter(type);
1842 if (currentFilter == null)
1844 currentFilter = new FeatureMatcherSet();
1846 Object[] data = ((FeatureTableModel) table.getModel())
1847 .getData()[rowSelected];
1848 data[COLOUR_COLUMN] = currentColor;
1849 data[FILTER_COLUMN] = currentFilter;
1850 fireEditingStopped();
1851 me.table.validate();
1856 public Object getCellEditorValue()
1858 return currentFilter;
1862 public Component getTableCellEditorComponent(JTable theTable, Object value,
1863 boolean isSelected, int row, int column)
1865 currentFilter = (FeatureMatcherSetI) value;
1866 this.rowSelected = row;
1867 type = me.table.getValueAt(row, TYPE_COLUMN).toString();
1868 button.setOpaque(true);
1869 button.setBackground(me.getBackground());
1870 button.setText(currentFilter.toString());
1871 button.setIcon(null);
1877 class FeatureIcon implements Icon
1879 FeatureColourI gcol;
1883 boolean midspace = false;
1885 int width = 50, height = 20;
1887 int s1, e1; // start and end of midpoint band for thresholded symbol
1889 Color mpcolour = Color.white;
1891 FeatureIcon(FeatureColourI gfc, Color bg, int w, int h, boolean mspace)
1911 public int getIconWidth()
1917 public int getIconHeight()
1923 public void paintIcon(Component c, Graphics g, int x, int y)
1926 if (gcol.isColourByLabel())
1929 g.fillRect(0, 0, width, height);
1930 // need an icon here.
1931 g.setColor(gcol.getMaxColour());
1933 g.setFont(new Font("Verdana", Font.PLAIN, 9));
1935 // g.setFont(g.getFont().deriveFont(
1936 // AffineTransform.getScaleInstance(
1937 // width/g.getFontMetrics().stringWidth("Label"),
1938 // height/g.getFontMetrics().getHeight())));
1940 g.drawString(MessageManager.getString("label.label"), 0, 0);
1945 Color minCol = gcol.getMinColour();
1947 g.fillRect(0, 0, s1, height);
1950 g.setColor(Color.white);
1951 g.fillRect(s1, 0, e1 - s1, height);
1953 g.setColor(gcol.getMaxColour());
1954 g.fillRect(0, e1, width - e1, height);