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.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.ToolTipManager;
96 import javax.swing.border.Border;
97 import javax.swing.event.ChangeEvent;
98 import javax.swing.event.ChangeListener;
99 import javax.swing.table.AbstractTableModel;
100 import javax.swing.table.TableCellEditor;
101 import javax.swing.table.TableCellRenderer;
102 import javax.swing.table.TableColumn;
104 public class FeatureSettings extends JPanel
105 implements FeatureSettingsControllerI
107 private static final String SEQUENCE_FEATURE_COLOURS = MessageManager
108 .getString("label.sequence_feature_colours");
111 * column indices of fields in Feature Settings table
113 static final int TYPE_COLUMN = 0;
115 static final int COLOUR_COLUMN = 1;
117 static final int FILTER_COLUMN = 2;
119 static final int SHOW_COLUMN = 3;
121 private static final int COLUMN_COUNT = 4;
123 private static final int MIN_WIDTH = 400;
125 private static final int MIN_HEIGHT = 400;
127 private final static String BASE_TOOLTIP = "Click to edit, right-click for menu";
129 final FeatureRenderer fr;
131 public final AlignFrame af;
134 * 'original' fields hold settings to restore on Cancel
136 Object[][] originalData;
138 float originalTransparency;
140 Map<String, FeatureMatcherSetI> originalFilters;
142 final JInternalFrame frame;
144 JScrollPane scrollPane = new JScrollPane();
150 JSlider transparency = new JSlider();
153 * when true, constructor is still executing - so ignore UI events
155 protected volatile boolean inConstruction = true;
157 int selectedRow = -1;
159 JButton fetchDAS = new JButton();
161 JButton saveDAS = new JButton();
163 JButton cancelDAS = new JButton();
165 boolean resettingTable = false;
168 * true when Feature Settings are updating from feature renderer
170 boolean handlingUpdate = false;
173 * holds {featureCount, totalExtent} for each feature type
175 Map<String, float[]> typeWidth = null;
182 public FeatureSettings(AlignFrame alignFrame)
184 this.af = alignFrame;
185 fr = af.getFeatureRenderer();
187 // save transparency for restore on Cancel
188 originalTransparency = fr.getTransparency();
189 int originalTransparencyAsPercent = (int) (originalTransparency * 100);
190 transparency.setMaximum(100 - originalTransparencyAsPercent);
192 originalFilters = new HashMap<>(fr.getFeatureFilters()); // shallow copy
197 } catch (Exception ex)
199 ex.printStackTrace();
204 static final String tt = "Click to edit, right-click for menu"; // todo i18n
207 public String getToolTipText(MouseEvent e)
210 int column = table.columnAtPoint(e.getPoint());
211 int row = table.rowAtPoint(e.getPoint());
216 tip = JvSwingUtils.wrapTooltip(true, MessageManager
217 .getString("label.feature_settings_click_drag"));
220 FeatureColourI colour = (FeatureColourI) table.getValueAt(row,
222 tip = getColorTooltip(colour, true);
225 FeatureMatcherSet o = (FeatureMatcherSet) table.getValueAt(row,
228 ? MessageManager.getString("label.filters_tooltip")
238 * Position the tooltip near the bottom edge of, and half way across, the
242 public Point getToolTipLocation(MouseEvent e)
244 Point point = e.getPoint();
245 int column = table.columnAtPoint(point);
246 int row = table.rowAtPoint(point);
247 Rectangle r = getCellRect(row, column, false);
248 Point loc = new Point(r.x + r.width / 2, r.y + r.height - 3);
252 table.getTableHeader().setFont(new Font("Verdana", Font.PLAIN, 12));
253 table.setFont(new Font("Verdana", Font.PLAIN, 12));
254 ToolTipManager.sharedInstance().registerComponent(table);
256 // table.setDefaultRenderer(Color.class, new ColorRenderer());
257 // table.setDefaultEditor(Color.class, new ColorEditor(this));
259 table.setDefaultEditor(FeatureColour.class, new ColorEditor(this));
260 table.setDefaultRenderer(FeatureColour.class, new ColorRenderer());
262 table.setDefaultEditor(FeatureMatcherSet.class, new FilterEditor(this));
263 table.setDefaultRenderer(FeatureMatcherSet.class, new FilterRenderer());
265 TableColumn colourColumn = new TableColumn(COLOUR_COLUMN, 75,
266 new ColorRenderer(), new ColorEditor(this));
267 table.addColumn(colourColumn);
269 TableColumn filterColumn = new TableColumn(FILTER_COLUMN, 75,
270 new FilterRenderer(), new FilterEditor(this));
271 table.addColumn(filterColumn);
273 table.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
275 table.addMouseListener(new MouseAdapter()
278 public void mousePressed(MouseEvent evt)
280 selectedRow = table.rowAtPoint(evt.getPoint());
281 String type = (String) table.getValueAt(selectedRow, TYPE_COLUMN);
282 if (evt.isPopupTrigger())
284 Object colour = table.getValueAt(selectedRow, COLOUR_COLUMN);
285 popupSort(selectedRow, type, colour, fr.getMinMax(), evt.getX(),
288 else if (evt.getClickCount() == 2)
290 boolean invertSelection = evt.isAltDown();
291 boolean toggleSelection = Platform.isControlDown(evt);
292 boolean extendSelection = evt.isShiftDown();
293 fr.ap.alignFrame.avc.markColumnsContainingFeatures(
294 invertSelection, extendSelection, toggleSelection, type);
298 // isPopupTrigger fires on mouseReleased on Windows
300 public void mouseReleased(MouseEvent evt)
302 selectedRow = table.rowAtPoint(evt.getPoint());
303 if (evt.isPopupTrigger())
305 String type = (String) table.getValueAt(selectedRow, TYPE_COLUMN);
306 Object colour = table.getValueAt(selectedRow, COLOUR_COLUMN);
307 popupSort(selectedRow, type, colour, fr.getMinMax(), evt.getX(),
313 table.addMouseMotionListener(new MouseMotionAdapter()
316 public void mouseDragged(MouseEvent evt)
318 int newRow = table.rowAtPoint(evt.getPoint());
319 if (newRow != selectedRow && selectedRow != -1 && newRow != -1)
322 * reposition 'selectedRow' to 'newRow' (the dragged to location)
323 * this could be more than one row away for a very fast drag action
324 * so just swap it with adjacent rows until we get it there
326 Object[][] data = ((FeatureTableModel) table.getModel())
328 int direction = newRow < selectedRow ? -1 : 1;
329 for (int i = selectedRow; i != newRow; i += direction)
331 Object[] temp = data[i];
332 data[i] = data[i + direction];
333 data[i + direction] = temp;
335 updateFeatureRenderer(data);
337 selectedRow = newRow;
341 // table.setToolTipText(JvSwingUtils.wrapTooltip(true,
342 // MessageManager.getString("label.feature_settings_click_drag")));
343 scrollPane.setViewportView(table);
345 if (af.getViewport().isShowSequenceFeatures() || !fr.hasRenderOrder())
347 fr.findAllFeatures(true); // display everything!
350 discoverAllFeatureData();
351 final PropertyChangeListener change;
352 final FeatureSettings fs = this;
353 fr.addPropertyChangeListener(change = new PropertyChangeListener()
356 public void propertyChange(PropertyChangeEvent evt)
358 if (!fs.resettingTable && !fs.handlingUpdate)
360 fs.handlingUpdate = true;
362 // new groups may be added with new sequence feature types only
363 fs.handlingUpdate = false;
369 frame = new JInternalFrame();
370 frame.setContentPane(this);
371 if (Platform.isAMac())
373 Desktop.addInternalFrame(frame,
374 MessageManager.getString("label.sequence_feature_settings"),
379 Desktop.addInternalFrame(frame,
380 MessageManager.getString("label.sequence_feature_settings"),
383 frame.setMinimumSize(new Dimension(MIN_WIDTH, MIN_HEIGHT));
385 frame.addInternalFrameListener(
386 new javax.swing.event.InternalFrameAdapter()
389 public void internalFrameClosed(
390 javax.swing.event.InternalFrameEvent evt)
392 fr.removePropertyChangeListener(change);
395 frame.setLayer(JLayeredPane.PALETTE_LAYER);
396 inConstruction = false;
399 protected void popupSort(final int rowSelected, final String type,
400 final Object typeCol, final Map<String, float[][]> minmax, int x,
403 final FeatureColourI featureColour = (FeatureColourI) typeCol;
405 JPopupMenu men = new JPopupMenu(MessageManager
406 .formatMessage("label.settings_for_param", new String[]
408 JMenuItem scr = new JMenuItem(
409 MessageManager.getString("label.sort_by_score"));
411 final FeatureSettings me = this;
412 scr.addActionListener(new ActionListener()
416 public void actionPerformed(ActionEvent e)
419 .sortAlignmentByFeatureScore(Arrays.asList(new String[]
424 JMenuItem dens = new JMenuItem(
425 MessageManager.getString("label.sort_by_density"));
426 dens.addActionListener(new ActionListener()
430 public void actionPerformed(ActionEvent e)
433 .sortAlignmentByFeatureDensity(Arrays.asList(new String[]
441 * variable colour options include colour by label, by score,
442 * by selected attribute text, or attribute value
444 final JCheckBoxMenuItem variableColourCB = new JCheckBoxMenuItem(
445 MessageManager.getString("label.variable_colour"));
446 variableColourCB.setSelected(!featureColour.isSimpleColour());
447 men.add(variableColourCB);
450 * checkbox action listener doubles up as listener to OK
451 * from the variable colour / filters dialog
453 variableColourCB.addActionListener(new ActionListener()
456 public void actionPerformed(ActionEvent e)
458 if (e.getSource() == variableColourCB)
460 if (featureColour.isSimpleColour())
463 * toggle simple colour to variable colour - show dialog
465 FeatureTypeSettings fc = new FeatureTypeSettings(me.fr, type);
466 fc.addActionListener(this);
471 * toggle variable to simple colour - show colour chooser
473 String title = MessageManager.getString("label.select_colour");
474 ColourChooserListener listener = new ColourChooserListener()
477 public void colourSelected(Color c)
479 table.setValueAt(new FeatureColour(c), rowSelected,
482 me.updateFeatureRenderer(
483 ((FeatureTableModel) table.getModel()).getData(),
487 JalviewColourChooser.showColourChooser(me, title, featureColour.getMaxColour(), listener);
491 if (e.getSource() instanceof FeatureTypeSettings)
494 * update after OK in feature colour dialog; the updated
495 * colour will have already been set in the FeatureRenderer
497 FeatureColourI fci = fr.getFeatureColours().get(type);
498 table.setValueAt(fci, rowSelected, COLOUR_COLUMN);
506 JMenuItem selCols = new JMenuItem(
507 MessageManager.getString("label.select_columns_containing"));
508 selCols.addActionListener(new ActionListener()
511 public void actionPerformed(ActionEvent arg0)
513 fr.ap.alignFrame.avc.markColumnsContainingFeatures(false, false,
517 JMenuItem clearCols = new JMenuItem(MessageManager
518 .getString("label.select_columns_not_containing"));
519 clearCols.addActionListener(new ActionListener()
522 public void actionPerformed(ActionEvent arg0)
524 fr.ap.alignFrame.avc.markColumnsContainingFeatures(true, false,
528 JMenuItem hideCols = new JMenuItem(
529 MessageManager.getString("label.hide_columns_containing"));
530 hideCols.addActionListener(new ActionListener()
533 public void actionPerformed(ActionEvent arg0)
535 fr.ap.alignFrame.hideFeatureColumns(type, true);
538 JMenuItem hideOtherCols = new JMenuItem(
539 MessageManager.getString("label.hide_columns_not_containing"));
540 hideOtherCols.addActionListener(new ActionListener()
543 public void actionPerformed(ActionEvent arg0)
545 fr.ap.alignFrame.hideFeatureColumns(type, false);
551 men.add(hideOtherCols);
552 men.show(table, x, y);
556 synchronized public void discoverAllFeatureData()
558 Set<String> allGroups = new HashSet<>();
559 AlignmentI alignment = af.getViewport().getAlignment();
561 for (int i = 0; i < alignment.getHeight(); i++)
563 SequenceI seq = alignment.getSequenceAt(i);
564 for (String group : seq.getFeatures().getFeatureGroups(true))
566 if (group != null && !allGroups.contains(group))
568 allGroups.add(group);
569 checkGroupState(group);
580 * Synchronise gui group list and check visibility of group
583 * @return true if group is visible
585 private boolean checkGroupState(String group)
587 boolean visible = fr.checkGroupVisibility(group, true);
589 for (int g = 0; g < groupPanel.getComponentCount(); g++)
591 if (((JCheckBox) groupPanel.getComponent(g)).getText().equals(group))
593 ((JCheckBox) groupPanel.getComponent(g)).setSelected(visible);
598 final String grp = group;
599 final JCheckBox check = new JCheckBox(group, visible);
600 check.setFont(new Font("Serif", Font.BOLD, 12));
601 check.setToolTipText(group);
602 check.addItemListener(new ItemListener()
605 public void itemStateChanged(ItemEvent evt)
607 fr.setGroupVisibility(check.getText(), check.isSelected());
608 resetTable(new String[] { grp });
609 af.alignPanel.paintAlignment(true, true);
612 groupPanel.add(check);
616 synchronized void resetTable(String[] groupChanged)
622 resettingTable = true;
623 typeWidth = new Hashtable<>();
624 // TODO: change avWidth calculation to 'per-sequence' average and use long
627 Set<String> displayableTypes = new HashSet<>();
628 Set<String> foundGroups = new HashSet<>();
631 * determine which feature types may be visible depending on
632 * which groups are selected, and recompute average width data
634 for (int i = 0; i < af.getViewport().getAlignment().getHeight(); i++)
637 SequenceI seq = af.getViewport().getAlignment().getSequenceAt(i);
640 * get the sequence's groups for positional features
641 * and keep track of which groups are visible
643 Set<String> groups = seq.getFeatures().getFeatureGroups(true);
644 Set<String> visibleGroups = new HashSet<>();
645 for (String group : groups)
647 if (group == null || checkGroupState(group))
649 visibleGroups.add(group);
652 foundGroups.addAll(groups);
655 * get distinct feature types for visible groups
656 * record distinct visible types, and their count and total length
658 Set<String> types = seq.getFeatures().getFeatureTypesForGroups(true,
659 visibleGroups.toArray(new String[visibleGroups.size()]));
660 for (String type : types)
662 displayableTypes.add(type);
663 float[] avWidth = typeWidth.get(type);
666 avWidth = new float[2];
667 typeWidth.put(type, avWidth);
669 // todo this could include features with a non-visible group
670 // - do we greatly care?
671 // todo should we include non-displayable features here, and only
672 // update when features are added?
673 avWidth[0] += seq.getFeatures().getFeatureCount(true, type);
674 avWidth[1] += seq.getFeatures().getTotalFeatureLength(type);
678 Object[][] data = new Object[displayableTypes.size()][COLUMN_COUNT];
681 if (fr.hasRenderOrder())
685 fr.findAllFeatures(groupChanged != null); // prod to update
686 // colourschemes. but don't
688 // First add the checks in the previous render order,
689 // in case the window has been closed and reopened
691 List<String> frl = fr.getRenderOrder();
692 for (int ro = frl.size() - 1; ro > -1; ro--)
694 String type = frl.get(ro);
696 if (!displayableTypes.contains(type))
701 data[dataIndex][TYPE_COLUMN] = type;
702 data[dataIndex][COLOUR_COLUMN] = fr.getFeatureStyle(type);
703 FeatureMatcherSetI featureFilter = fr.getFeatureFilter(type);
704 data[dataIndex][FILTER_COLUMN] = featureFilter == null
705 ? new FeatureMatcherSet()
707 data[dataIndex][SHOW_COLUMN] = new Boolean(
708 af.getViewport().getFeaturesDisplayed().isVisible(type));
710 displayableTypes.remove(type);
715 * process any extra features belonging only to
716 * a group which was just selected
718 while (!displayableTypes.isEmpty())
720 String type = displayableTypes.iterator().next();
721 data[dataIndex][TYPE_COLUMN] = type;
723 data[dataIndex][COLOUR_COLUMN] = fr.getFeatureStyle(type);
724 if (data[dataIndex][COLOUR_COLUMN] == null)
726 // "Colour has been updated in another view!!"
727 fr.clearRenderOrder();
730 FeatureMatcherSetI featureFilter = fr.getFeatureFilter(type);
731 data[dataIndex][FILTER_COLUMN] = featureFilter == null
732 ? new FeatureMatcherSet()
734 data[dataIndex][SHOW_COLUMN] = new Boolean(true);
736 displayableTypes.remove(type);
739 if (originalData == null)
741 originalData = new Object[data.length][COLUMN_COUNT];
742 for (int i = 0; i < data.length; i++)
744 System.arraycopy(data[i], 0, originalData[i], 0, COLUMN_COUNT);
749 updateOriginalData(data);
752 table.setModel(new FeatureTableModel(data));
753 table.getColumnModel().getColumn(0).setPreferredWidth(200);
755 groupPanel.setLayout(
756 new GridLayout(fr.getFeatureGroupsSize() / 4 + 1, 4));
757 pruneGroups(foundGroups);
758 groupPanel.validate();
760 updateFeatureRenderer(data, groupChanged != null);
761 resettingTable = false;
765 * Updates 'originalData' (used for restore on Cancel) if we detect that changes
766 * have been made outwith this dialog
768 * <li>a new feature type added (and made visible)</li>
769 * <li>a feature colour changed (in the Amend Features dialog)</li>
774 protected void updateOriginalData(Object[][] foundData)
776 // todo LinkedHashMap instead of Object[][] would be nice
778 Object[][] currentData = ((FeatureTableModel) table.getModel())
780 for (Object[] row : foundData)
782 String type = (String) row[TYPE_COLUMN];
783 boolean found = false;
784 for (Object[] current : currentData)
786 if (type.equals(current[TYPE_COLUMN]))
790 * currently dependent on object equality here;
791 * really need an equals method on FeatureColour
793 if (!row[COLOUR_COLUMN].equals(current[COLOUR_COLUMN]))
796 * feature colour has changed externally - update originalData
798 for (Object[] original : originalData)
800 if (type.equals(original[TYPE_COLUMN]))
802 original[COLOUR_COLUMN] = row[COLOUR_COLUMN];
813 * new feature detected - add to original data (on top)
815 Object[][] newData = new Object[originalData.length
817 for (int i = 0; i < originalData.length; i++)
819 System.arraycopy(originalData[i], 0, newData[i + 1], 0,
823 originalData = newData;
829 * Remove from the groups panel any checkboxes for groups that are not in the
830 * foundGroups set. This enables removing a group from the display when the last
831 * feature in that group is deleted.
835 protected void pruneGroups(Set<String> foundGroups)
837 for (int g = 0; g < groupPanel.getComponentCount(); g++)
839 JCheckBox checkbox = (JCheckBox) groupPanel.getComponent(g);
840 if (!foundGroups.contains(checkbox.getText()))
842 groupPanel.remove(checkbox);
848 * reorder data based on the featureRenderers global priority list.
852 private void ensureOrder(Object[][] data)
854 boolean sort = false;
855 float[] order = new float[data.length];
856 for (int i = 0; i < order.length; i++)
858 order[i] = fr.getOrder(data[i][0].toString());
861 order[i] = fr.setOrder(data[i][0].toString(), i / order.length);
865 sort = sort || order[i - 1] > order[i];
870 jalview.util.QuickSort.sort(order, data);
875 * Offers a file chooser dialog, and then loads the feature colours and
876 * filters from file in XML format and unmarshals to Jalview feature settings
880 // TODO: JAL-3048 relies on Castor XML parsing: not needed for JS-jalview core
883 JalviewFileChooser chooser = new JalviewFileChooser("fc",
884 SEQUENCE_FEATURE_COLOURS);
885 chooser.setFileView(new JalviewFileView());
886 chooser.setDialogTitle(
887 MessageManager.getString("label.load_feature_colours"));
888 chooser.setToolTipText(MessageManager.getString("action.load"));
890 int value = chooser.showOpenDialog(this);
892 if (value == JalviewFileChooser.APPROVE_OPTION)
894 File file = chooser.getSelectedFile();
900 * Loads feature colours and filters from XML stored in the given file
908 InputStreamReader in = new InputStreamReader(
909 new FileInputStream(file), "UTF-8");
911 JalviewUserColours jucs = JalviewUserColours.unmarshal(in);
914 * load feature colours
916 for (int i = jucs.getColourCount() - 1; i >= 0; i--)
918 jalview.schemabinding.version2.Colour newcol = jucs.getColour(i);
919 FeatureColourI colour = Jalview2XML.unmarshalColour(newcol);
920 fr.setColour(newcol.getName(), colour);
921 fr.setOrder(newcol.getName(), i / (float) jucs.getColourCount());
925 * load feature filters; loaded filters will replace any that are
926 * currently defined, other defined filters are left unchanged
928 for (int i = 0; i < jucs.getFilterCount(); i++)
930 jalview.schemabinding.version2.Filter filterModel = jucs
932 String featureType = filterModel.getFeatureType();
933 FeatureMatcherSetI filter = Jalview2XML.unmarshalFilter(featureType,
934 filterModel.getMatcherSet());
935 if (!filter.isEmpty())
937 fr.setFeatureFilter(featureType, filter);
942 * update feature settings table
947 Object[][] data = ((FeatureTableModel) table.getModel())
950 updateFeatureRenderer(data, false);
953 } catch (Exception ex)
955 System.out.println("Error loading User Colour File\n" + ex);
960 * Offers a file chooser dialog, and then saves the current feature colours
961 * and any filters to the selected file in XML format
965 // TODO: JAL-3048 not needed for Jalview-JS - save colours
966 JalviewFileChooser chooser = new JalviewFileChooser("fc",
967 SEQUENCE_FEATURE_COLOURS);
968 chooser.setFileView(new JalviewFileView());
969 chooser.setDialogTitle(
970 MessageManager.getString("label.save_feature_colours"));
971 chooser.setToolTipText(MessageManager.getString("action.save"));
973 int value = chooser.showSaveDialog(this);
975 if (value == JalviewFileChooser.APPROVE_OPTION)
977 save(chooser.getSelectedFile());
982 * Saves feature colours and filters to the given file
988 JalviewUserColours ucs = new JalviewUserColours();
989 ucs.setSchemeName("Sequence Features");
992 PrintWriter out = new PrintWriter(new OutputStreamWriter(
993 new FileOutputStream(file), "UTF-8"));
996 * sort feature types by colour order, from 0 (highest)
999 Set<String> fr_colours = fr.getAllFeatureColours();
1000 String[] sortedTypes = fr_colours
1001 .toArray(new String[fr_colours.size()]);
1002 Arrays.sort(sortedTypes, new Comparator<String>()
1005 public int compare(String type1, String type2)
1007 return Float.compare(fr.getOrder(type1), fr.getOrder(type2));
1012 * save feature colours
1014 for (String featureType : sortedTypes)
1016 FeatureColourI fcol = fr.getFeatureStyle(featureType);
1017 jalview.schemabinding.version2.Colour col = Jalview2XML.marshalColour(
1023 * save any feature filters
1025 for (String featureType : sortedTypes)
1027 FeatureMatcherSetI filter = fr.getFeatureFilter(featureType);
1028 if (filter != null && !filter.isEmpty())
1030 Iterator<FeatureMatcherI> iterator = filter.getMatchers().iterator();
1031 FeatureMatcherI firstMatcher = iterator.next();
1032 MatcherSet ms = Jalview2XML.marshalFilter(firstMatcher, iterator,
1034 Filter filterModel = new Filter();
1035 filterModel.setFeatureType(featureType);
1036 filterModel.setMatcherSet(ms);
1037 ucs.addFilter(filterModel);
1043 } catch (Exception ex)
1045 ex.printStackTrace();
1049 public void invertSelection()
1051 Object[][] data = ((FeatureTableModel) table.getModel()).getData();
1052 for (int i = 0; i < data.length; i++)
1054 data[i][SHOW_COLUMN] = !(Boolean) data[i][SHOW_COLUMN];
1056 updateFeatureRenderer(data, true);
1060 public void orderByAvWidth()
1062 if (table == null || table.getModel() == null)
1066 Object[][] data = ((FeatureTableModel) table.getModel()).getData();
1067 float[] width = new float[data.length];
1071 for (int i = 0; i < data.length; i++)
1073 awidth = typeWidth.get(data[i][TYPE_COLUMN]);
1076 width[i] = awidth[1] / awidth[0];// *awidth[0]*awidth[2]; - better
1077 // weight - but have to make per
1078 // sequence, too (awidth[2])
1079 // if (width[i]==1) // hack to distinguish single width sequences.
1090 boolean sort = false;
1091 for (int i = 0; i < width.length; i++)
1093 // awidth = (float[]) typeWidth.get(data[i][0]);
1096 width[i] = fr.getOrder(data[i][TYPE_COLUMN].toString());
1099 width[i] = fr.setOrder(data[i][TYPE_COLUMN].toString(),
1105 width[i] /= max; // normalize
1106 fr.setOrder(data[i][TYPE_COLUMN].toString(), width[i]); // store for later
1110 sort = sort || width[i - 1] > width[i];
1115 jalview.util.QuickSort.sort(width, data);
1116 // update global priority order
1119 updateFeatureRenderer(data, false);
1127 frame.setClosed(true);
1128 } catch (Exception exe)
1134 public void updateFeatureRenderer(Object[][] data)
1136 updateFeatureRenderer(data, true);
1140 * Update the priority order of features; only repaint if this changed the order
1141 * of visible features
1146 void updateFeatureRenderer(Object[][] data, boolean visibleNew)
1148 FeatureSettingsBean[] rowData = getTableAsBeans(data);
1150 if (fr.setFeaturePriority(rowData, visibleNew))
1152 af.alignPanel.paintAlignment(true, true);
1157 * Converts table data into an array of data beans
1159 private FeatureSettingsBean[] getTableAsBeans(Object[][] data)
1161 FeatureSettingsBean[] rowData = new FeatureSettingsBean[data.length];
1162 for (int i = 0; i < data.length; i++)
1164 String type = (String) data[i][TYPE_COLUMN];
1165 FeatureColourI colour = (FeatureColourI) data[i][COLOUR_COLUMN];
1166 FeatureMatcherSetI theFilter = (FeatureMatcherSetI) data[i][FILTER_COLUMN];
1167 Boolean isShown = (Boolean) data[i][SHOW_COLUMN];
1168 rowData[i] = new FeatureSettingsBean(type, colour, theFilter,
1174 private void jbInit() throws Exception
1176 this.setLayout(new BorderLayout());
1178 JPanel settingsPane = new JPanel();
1179 settingsPane.setLayout(new BorderLayout());
1181 JPanel bigPanel = new JPanel();
1182 bigPanel.setLayout(new BorderLayout());
1184 groupPanel = new JPanel();
1185 bigPanel.add(groupPanel, BorderLayout.NORTH);
1187 JButton invert = new JButton(
1188 MessageManager.getString("label.invert_selection"));
1189 invert.setFont(JvSwingUtils.getLabelFont());
1190 invert.addActionListener(new ActionListener()
1193 public void actionPerformed(ActionEvent e)
1199 JButton optimizeOrder = new JButton(
1200 MessageManager.getString("label.optimise_order"));
1201 optimizeOrder.setFont(JvSwingUtils.getLabelFont());
1202 optimizeOrder.addActionListener(new ActionListener()
1205 public void actionPerformed(ActionEvent e)
1211 JButton sortByScore = new JButton(
1212 MessageManager.getString("label.seq_sort_by_score"));
1213 sortByScore.setFont(JvSwingUtils.getLabelFont());
1214 sortByScore.addActionListener(new ActionListener()
1217 public void actionPerformed(ActionEvent e)
1219 af.avc.sortAlignmentByFeatureScore(null);
1222 JButton sortByDens = new JButton(
1223 MessageManager.getString("label.sequence_sort_by_density"));
1224 sortByDens.setFont(JvSwingUtils.getLabelFont());
1225 sortByDens.addActionListener(new ActionListener()
1228 public void actionPerformed(ActionEvent e)
1230 af.avc.sortAlignmentByFeatureDensity(null);
1234 JButton help = new JButton(MessageManager.getString("action.help"));
1235 help.setFont(JvSwingUtils.getLabelFont());
1236 help.addActionListener(new ActionListener()
1239 public void actionPerformed(ActionEvent e)
1243 Help.showHelpWindow(HelpId.SequenceFeatureSettings);
1244 } catch (HelpSetException e1)
1246 e1.printStackTrace();
1250 help.setFont(JvSwingUtils.getLabelFont());
1251 help.setText(MessageManager.getString("action.help"));
1252 help.addActionListener(new ActionListener()
1255 public void actionPerformed(ActionEvent e)
1259 Help.showHelpWindow(HelpId.SequenceFeatureSettings);
1260 } catch (HelpSetException e1)
1262 e1.printStackTrace();
1267 JButton cancel = new JButton(MessageManager.getString("action.cancel"));
1268 cancel.setFont(JvSwingUtils.getLabelFont());
1269 cancel.addActionListener(new ActionListener()
1272 public void actionPerformed(ActionEvent e)
1274 fr.setTransparency(originalTransparency);
1275 fr.setFeatureFilters(originalFilters);
1276 updateFeatureRenderer(originalData);
1281 JButton ok = new JButton(MessageManager.getString("action.ok"));
1282 ok.setFont(JvSwingUtils.getLabelFont());
1283 ok.addActionListener(new ActionListener()
1286 public void actionPerformed(ActionEvent e)
1292 JButton loadColours = new JButton(
1293 MessageManager.getString("label.load_colours"));
1294 loadColours.setFont(JvSwingUtils.getLabelFont());
1295 loadColours.setToolTipText(
1296 MessageManager.getString("label.load_colours_tooltip"));
1297 loadColours.addActionListener(new ActionListener()
1300 public void actionPerformed(ActionEvent e)
1306 JButton saveColours = new JButton(
1307 MessageManager.getString("label.save_colours"));
1308 saveColours.setFont(JvSwingUtils.getLabelFont());
1309 saveColours.setToolTipText(
1310 MessageManager.getString("label.save_colours_tooltip"));
1311 saveColours.addActionListener(new ActionListener()
1314 public void actionPerformed(ActionEvent e)
1319 transparency.addChangeListener(new ChangeListener()
1322 public void stateChanged(ChangeEvent evt)
1324 if (!inConstruction)
1326 fr.setTransparency((100 - transparency.getValue()) / 100f);
1327 af.alignPanel.paintAlignment(true, true);
1332 transparency.setMaximum(70);
1333 transparency.setToolTipText(
1334 MessageManager.getString("label.transparency_tip"));
1336 JPanel transPanel = new JPanel(new GridLayout(1, 2));
1337 bigPanel.add(transPanel, BorderLayout.SOUTH);
1339 JPanel transbuttons = new JPanel(new GridLayout(5, 1));
1340 transbuttons.add(optimizeOrder);
1341 transbuttons.add(invert);
1342 transbuttons.add(sortByScore);
1343 transbuttons.add(sortByDens);
1344 transbuttons.add(help);
1345 transPanel.add(transparency);
1346 transPanel.add(transbuttons);
1348 JPanel buttonPanel = new JPanel();
1349 buttonPanel.add(ok);
1350 buttonPanel.add(cancel);
1351 buttonPanel.add(loadColours);
1352 buttonPanel.add(saveColours);
1353 bigPanel.add(scrollPane, BorderLayout.CENTER);
1354 settingsPane.add(bigPanel, BorderLayout.CENTER);
1355 settingsPane.add(buttonPanel, BorderLayout.SOUTH);
1356 this.add(settingsPane);
1360 * Answers a suitable tooltip to show on the colour cell of the table
1364 * if true include 'click to edit' and similar text
1367 public static String getColorTooltip(FeatureColourI fcol,
1374 if (fcol.isSimpleColour())
1376 return withHint ? BASE_TOOLTIP : null;
1378 String description = fcol.getDescription();
1379 description = description.replaceAll("<", "<");
1380 description = description.replaceAll(">", ">");
1381 StringBuilder tt = new StringBuilder(description);
1384 tt.append("<br>").append(BASE_TOOLTIP).append("</br>");
1386 return JvSwingUtils.wrapTooltip(true, tt.toString());
1389 public static void renderGraduatedColor(JLabel comp, FeatureColourI gcol,
1392 boolean thr = false;
1393 StringBuilder tx = new StringBuilder();
1395 if (gcol.isColourByAttribute())
1397 tx.append(FeatureMatcher
1398 .toAttributeDisplayName(gcol.getAttributeName()));
1400 else if (!gcol.isColourByLabel())
1402 tx.append(MessageManager.getString("label.score"));
1405 if (gcol.isAboveThreshold())
1410 if (gcol.isBelowThreshold())
1415 if (gcol.isColourByLabel())
1421 if (!gcol.isColourByAttribute())
1429 Color newColor = gcol.getMaxColour();
1430 comp.setBackground(newColor);
1431 // System.err.println("Width is " + w / 2);
1432 Icon ficon = new FeatureIcon(gcol, comp.getBackground(), w, h, thr);
1433 comp.setIcon(ficon);
1434 // tt+="RGB value: Max (" + newColor.getRed() + ", "
1435 // + newColor.getGreen() + ", " + newColor.getBlue()
1436 // + ")\nMin (" + minCol.getRed() + ", " + minCol.getGreen()
1437 // + ", " + minCol.getBlue() + ")");
1439 comp.setHorizontalAlignment(SwingConstants.CENTER);
1440 comp.setText(tx.toString());
1443 // ///////////////////////////////////////////////////////////////////////
1444 // http://java.sun.com/docs/books/tutorial/uiswing/components/table.html
1445 // ///////////////////////////////////////////////////////////////////////
1446 class FeatureTableModel extends AbstractTableModel
1448 private String[] columnNames = {
1449 MessageManager.getString("label.feature_type"),
1450 MessageManager.getString("action.colour"),
1451 MessageManager.getString("label.filter"),
1452 MessageManager.getString("label.show") };
1454 private Object[][] data;
1456 FeatureTableModel(Object[][] data)
1461 public Object[][] getData()
1466 public void setData(Object[][] data)
1472 public int getColumnCount()
1474 return columnNames.length;
1477 public Object[] getRow(int row)
1483 public int getRowCount()
1489 public String getColumnName(int col)
1491 return columnNames[col];
1495 public Object getValueAt(int row, int col)
1497 return data[row][col];
1501 * Answers the class of the object in column c of the first row of the table
1504 public Class<?> getColumnClass(int c)
1506 Object v = getValueAt(0, c);
1507 return v == null ? null : v.getClass();
1511 public boolean isCellEditable(int row, int col)
1513 return col == 0 ? false : true;
1517 public void setValueAt(Object value, int row, int col)
1519 data[row][col] = value;
1520 fireTableCellUpdated(row, col);
1521 updateFeatureRenderer(data);
1526 class ColorRenderer extends JLabel implements TableCellRenderer
1528 Border unselectedBorder = null;
1530 Border selectedBorder = null;
1532 public ColorRenderer()
1534 setOpaque(true); // MUST do this for background to show up.
1535 setHorizontalTextPosition(SwingConstants.CENTER);
1536 setVerticalTextPosition(SwingConstants.CENTER);
1540 public Component getTableCellRendererComponent(JTable tbl, Object color,
1541 boolean isSelected, boolean hasFocus, int row, int column)
1543 FeatureColourI cellColour = (FeatureColourI) color;
1545 setBackground(tbl.getBackground());
1546 if (!cellColour.isSimpleColour())
1548 Rectangle cr = tbl.getCellRect(row, column, false);
1549 FeatureSettings.renderGraduatedColor(this, cellColour,
1550 (int) cr.getWidth(), (int) cr.getHeight());
1556 setBackground(cellColour.getColour());
1560 if (selectedBorder == null)
1562 selectedBorder = BorderFactory.createMatteBorder(2, 5, 2, 5,
1563 tbl.getSelectionBackground());
1565 setBorder(selectedBorder);
1569 if (unselectedBorder == null)
1571 unselectedBorder = BorderFactory.createMatteBorder(2, 5, 2, 5,
1572 tbl.getBackground());
1574 setBorder(unselectedBorder);
1581 class FilterRenderer extends JLabel implements TableCellRenderer
1583 javax.swing.border.Border unselectedBorder = null;
1585 javax.swing.border.Border selectedBorder = null;
1587 public FilterRenderer()
1589 setOpaque(true); // MUST do this for background to show up.
1590 setHorizontalTextPosition(SwingConstants.CENTER);
1591 setVerticalTextPosition(SwingConstants.CENTER);
1595 public Component getTableCellRendererComponent(JTable tbl,
1596 Object filter, boolean isSelected, boolean hasFocus, int row,
1599 FeatureMatcherSetI theFilter = (FeatureMatcherSetI) filter;
1601 String asText = theFilter.toString();
1602 setBackground(tbl.getBackground());
1603 this.setText(asText);
1608 if (selectedBorder == null)
1610 selectedBorder = BorderFactory.createMatteBorder(2, 5, 2, 5,
1611 tbl.getSelectionBackground());
1613 setBorder(selectedBorder);
1617 if (unselectedBorder == null)
1619 unselectedBorder = BorderFactory.createMatteBorder(2, 5, 2, 5,
1620 tbl.getBackground());
1622 setBorder(unselectedBorder);
1630 * update comp using rendering settings from gcol
1635 public static void renderGraduatedColor(JLabel comp, FeatureColourI gcol)
1637 int w = comp.getWidth(), h = comp.getHeight();
1640 w = (int) comp.getPreferredSize().getWidth();
1641 h = (int) comp.getPreferredSize().getHeight();
1648 renderGraduatedColor(comp, gcol, w, h);
1651 class ColorEditor extends AbstractCellEditor
1652 implements TableCellEditor, ActionListener
1656 FeatureColourI currentColor;
1658 FeatureTypeSettings chooser;
1664 protected static final String EDIT = "edit";
1666 int rowSelected = 0;
1668 public ColorEditor(FeatureSettings fs)
1671 // Set up the editor (from the table's point of view),
1672 // which is a button.
1673 // This button brings up the color chooser dialog,
1674 // which is the editor from the user's point of view.
1675 button = new JButton();
1676 button.setActionCommand(EDIT);
1677 button.addActionListener(this);
1678 button.setBorderPainted(false);
1682 * Handles events from the editor button, and from the colour/filters
1683 * dialog's OK button
1686 public void actionPerformed(ActionEvent e)
1688 if (button == e.getSource())
1690 if (currentColor.isSimpleColour())
1693 * simple colour chooser
1695 String ttl = MessageManager.getString("label.select_colour");
1696 ColourChooserListener listener = new ColourChooserListener() {
1698 public void colourSelected(Color c)
1700 currentColor = new FeatureColour(c);
1701 me.table.setValueAt(currentColor, rowSelected, COLOUR_COLUMN);
1704 JalviewColourChooser.showColourChooser(button, ttl, currentColor.getColour(), listener);
1709 * variable colour and filters dialog
1711 chooser = new FeatureTypeSettings(me.fr, type);
1716 chooser.setRequestFocusEnabled(true);
1717 chooser.requestFocus();
1719 chooser.addActionListener(this);
1720 // Make the renderer reappear.
1721 fireEditingStopped();
1727 * after OK in variable colour dialog, any changes to colour
1728 * (or filters!) are already set in FeatureRenderer, so just
1729 * update table data without triggering updateFeatureRenderer
1731 currentColor = fr.getFeatureColours().get(type);
1732 FeatureMatcherSetI currentFilter = me.fr.getFeatureFilter(type);
1733 if (currentFilter == null)
1735 currentFilter = new FeatureMatcherSet();
1737 Object[] data = ((FeatureTableModel) table.getModel())
1738 .getData()[rowSelected];
1739 data[COLOUR_COLUMN] = currentColor;
1740 data[FILTER_COLUMN] = currentFilter;
1742 fireEditingStopped();
1743 me.table.validate();
1747 // Implement the one CellEditor method that AbstractCellEditor doesn't.
1749 public Object getCellEditorValue()
1751 return currentColor;
1754 // Implement the one method defined by TableCellEditor.
1756 public Component getTableCellEditorComponent(JTable theTable, Object value,
1757 boolean isSelected, int row, int column)
1759 currentColor = (FeatureColourI) value;
1760 this.rowSelected = row;
1761 type = me.table.getValueAt(row, TYPE_COLUMN).toString();
1762 button.setOpaque(true);
1763 button.setBackground(me.getBackground());
1764 if (!currentColor.isSimpleColour())
1766 JLabel btn = new JLabel();
1767 btn.setSize(button.getSize());
1768 FeatureSettings.renderGraduatedColor(btn, currentColor);
1769 button.setBackground(btn.getBackground());
1770 button.setIcon(btn.getIcon());
1771 button.setText(btn.getText());
1776 button.setIcon(null);
1777 button.setBackground(currentColor.getColour());
1784 * The cell editor for the Filter column. It displays the text of any filters
1785 * for the feature type in that row (in full as a tooltip, possible abbreviated
1786 * as display text). On click in the cell, opens the Feature Display Settings
1787 * dialog at the Filters tab.
1789 class FilterEditor extends AbstractCellEditor
1790 implements TableCellEditor, ActionListener
1794 FeatureMatcherSetI currentFilter;
1802 protected static final String EDIT = "edit";
1804 int rowSelected = 0;
1806 public FilterEditor(FeatureSettings me)
1809 button = new JButton();
1810 button.setActionCommand(EDIT);
1811 button.addActionListener(this);
1812 button.setBorderPainted(false);
1816 * Handles events from the editor button
1819 public void actionPerformed(ActionEvent e)
1821 if (button == e.getSource())
1823 FeatureTypeSettings chooser = new FeatureTypeSettings(me.fr, type);
1824 chooser.addActionListener(this);
1825 chooser.setRequestFocusEnabled(true);
1826 chooser.requestFocus();
1827 if (lastLocation != null)
1829 // todo open at its last position on screen
1830 chooser.setBounds(lastLocation.x, lastLocation.y,
1831 chooser.getWidth(), chooser.getHeight());
1834 fireEditingStopped();
1836 else if (e.getSource() instanceof Component)
1840 * after OK in variable colour dialog, any changes to filter
1841 * (or colours!) are already set in FeatureRenderer, so just
1842 * update table data without triggering updateFeatureRenderer
1844 FeatureColourI currentColor = fr.getFeatureColours().get(type);
1845 currentFilter = me.fr.getFeatureFilter(type);
1846 if (currentFilter == null)
1848 currentFilter = new FeatureMatcherSet();
1850 Object[] data = ((FeatureTableModel) table.getModel())
1851 .getData()[rowSelected];
1852 data[COLOUR_COLUMN] = currentColor;
1853 data[FILTER_COLUMN] = currentFilter;
1854 fireEditingStopped();
1855 me.table.validate();
1860 public Object getCellEditorValue()
1862 return currentFilter;
1866 public Component getTableCellEditorComponent(JTable theTable, Object value,
1867 boolean isSelected, int row, int column)
1869 currentFilter = (FeatureMatcherSetI) value;
1870 this.rowSelected = row;
1871 type = me.table.getValueAt(row, TYPE_COLUMN).toString();
1872 button.setOpaque(true);
1873 button.setBackground(me.getBackground());
1874 button.setText(currentFilter.toString());
1875 button.setIcon(null);
1881 class FeatureIcon implements Icon
1883 FeatureColourI gcol;
1887 boolean midspace = false;
1889 int width = 50, height = 20;
1891 int s1, e1; // start and end of midpoint band for thresholded symbol
1893 Color mpcolour = Color.white;
1895 FeatureIcon(FeatureColourI gfc, Color bg, int w, int h, boolean mspace)
1915 public int getIconWidth()
1921 public int getIconHeight()
1927 public void paintIcon(Component c, Graphics g, int x, int y)
1930 if (gcol.isColourByLabel())
1933 g.fillRect(0, 0, width, height);
1934 // need an icon here.
1935 g.setColor(gcol.getMaxColour());
1937 g.setFont(new Font("Verdana", Font.PLAIN, 9));
1939 // g.setFont(g.getFont().deriveFont(
1940 // AffineTransform.getScaleInstance(
1941 // width/g.getFontMetrics().stringWidth("Label"),
1942 // height/g.getFontMetrics().getHeight())));
1944 g.drawString(MessageManager.getString("label.label"), 0, 0);
1949 Color minCol = gcol.getMinColour();
1951 g.fillRect(0, 0, s1, height);
1954 g.setColor(Color.white);
1955 g.fillRect(s1, 0, e1 - s1, height);
1957 g.setColor(gcol.getMaxColour());
1958 g.fillRect(0, e1, width - e1, height);