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.border.Border;
98 import javax.swing.event.ChangeEvent;
99 import javax.swing.event.ChangeListener;
100 import javax.swing.table.AbstractTableModel;
101 import javax.swing.table.TableCellEditor;
102 import javax.swing.table.TableCellRenderer;
103 import javax.swing.table.TableColumn;
105 public class FeatureSettings extends JPanel
106 implements FeatureSettingsControllerI
108 private static final String SEQUENCE_FEATURE_COLOURS = MessageManager
109 .getString("label.sequence_feature_colours");
112 * column indices of fields in Feature Settings table
114 static final int TYPE_COLUMN = 0;
116 static final int COLOUR_COLUMN = 1;
118 static final int FILTER_COLUMN = 2;
120 static final int SHOW_COLUMN = 3;
122 private static final int COLUMN_COUNT = 4;
124 private static final int MIN_WIDTH = 400;
126 private static final int MIN_HEIGHT = 400;
128 private final static String BASE_TOOLTIP = "Click to edit, right-click for menu";
130 final FeatureRenderer fr;
132 public final AlignFrame af;
135 * 'original' fields hold settings to restore on Cancel
137 Object[][] originalData;
139 float originalTransparency;
141 Map<String, FeatureMatcherSetI> originalFilters;
143 final JInternalFrame frame;
145 JScrollPane scrollPane = new JScrollPane();
151 JSlider transparency = new JSlider();
154 * when true, constructor is still executing - so ignore UI events
156 protected volatile boolean inConstruction = true;
158 int selectedRow = -1;
160 JButton fetchDAS = new JButton();
162 JButton saveDAS = new JButton();
164 JButton cancelDAS = new JButton();
166 boolean resettingTable = false;
169 * true when Feature Settings are updating from feature renderer
171 boolean handlingUpdate = false;
174 * holds {featureCount, totalExtent} for each feature type
176 Map<String, float[]> typeWidth = null;
183 public FeatureSettings(AlignFrame alignFrame)
185 this.af = alignFrame;
186 fr = af.getFeatureRenderer();
188 // save transparency for restore on Cancel
189 originalTransparency = fr.getTransparency();
190 int originalTransparencyAsPercent = (int) (originalTransparency * 100);
191 transparency.setMaximum(100 - originalTransparencyAsPercent);
193 originalFilters = new HashMap<>(fr.getFeatureFilters()); // shallow copy
198 } catch (Exception ex)
200 ex.printStackTrace();
205 static final String tt = "Click to edit, right-click for menu"; // todo i18n
208 public String getToolTipText(MouseEvent e)
211 int column = table.columnAtPoint(e.getPoint());
212 int row = table.rowAtPoint(e.getPoint());
217 tip = JvSwingUtils.wrapTooltip(true, MessageManager
218 .getString("label.feature_settings_click_drag"));
221 FeatureColourI colour = (FeatureColourI) table.getValueAt(row,
223 tip = getColorTooltip(colour);
226 FeatureMatcherSet o = (FeatureMatcherSet) table.getValueAt(row,
229 ? MessageManager.getString("label.filters_tooltip")
239 * Position the tooltip at the bottom edge of, and half way across, the
243 public Point getToolTipLocation(MouseEvent e)
245 Point point = e.getPoint();
246 int column = table.columnAtPoint(point);
247 int row = table.rowAtPoint(point);
248 Rectangle r = getCellRect(row, column, false);
249 Point loc = new Point(r.x + r.width / 2, r.y + r.height);
253 table.getTableHeader().setFont(new Font("Verdana", Font.PLAIN, 12));
254 table.setFont(new Font("Verdana", Font.PLAIN, 12));
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
1365 public static String getColorTooltip(FeatureColourI fcol)
1371 if (fcol.isSimpleColour())
1373 return BASE_TOOLTIP;
1375 String description = fcol.getDescription();
1376 description = description.replaceAll("<", "<");
1377 description = description.replaceAll(">", ">");
1378 StringBuilder tt = new StringBuilder(description);
1379 tt.append("<br>").append(BASE_TOOLTIP).append("</br>");
1380 return JvSwingUtils.wrapTooltip(true, tt.toString());
1383 public static void renderGraduatedColor(JLabel comp, FeatureColourI gcol,
1386 boolean thr = false;
1387 StringBuilder tx = new StringBuilder();
1389 if (gcol.isColourByAttribute())
1391 tx.append(FeatureMatcher
1392 .toAttributeDisplayName(gcol.getAttributeName()));
1394 else if (!gcol.isColourByLabel())
1396 tx.append(MessageManager.getString("label.score"));
1399 if (gcol.isAboveThreshold())
1404 if (gcol.isBelowThreshold())
1409 if (gcol.isColourByLabel())
1415 if (!gcol.isColourByAttribute())
1423 Color newColor = gcol.getMaxColour();
1424 comp.setBackground(newColor);
1425 // System.err.println("Width is " + w / 2);
1426 Icon ficon = new FeatureIcon(gcol, comp.getBackground(), w, h, thr);
1427 comp.setIcon(ficon);
1428 // tt+="RGB value: Max (" + newColor.getRed() + ", "
1429 // + newColor.getGreen() + ", " + newColor.getBlue()
1430 // + ")\nMin (" + minCol.getRed() + ", " + minCol.getGreen()
1431 // + ", " + minCol.getBlue() + ")");
1433 comp.setHorizontalAlignment(SwingConstants.CENTER);
1434 comp.setText(tx.toString());
1437 // ///////////////////////////////////////////////////////////////////////
1438 // http://java.sun.com/docs/books/tutorial/uiswing/components/table.html
1439 // ///////////////////////////////////////////////////////////////////////
1440 class FeatureTableModel extends AbstractTableModel
1442 private String[] columnNames = {
1443 MessageManager.getString("label.feature_type"),
1444 MessageManager.getString("action.colour"),
1445 MessageManager.getString("label.filter"),
1446 MessageManager.getString("label.show") };
1448 private Object[][] data;
1450 FeatureTableModel(Object[][] data)
1455 public Object[][] getData()
1460 public void setData(Object[][] data)
1466 public int getColumnCount()
1468 return columnNames.length;
1471 public Object[] getRow(int row)
1477 public int getRowCount()
1483 public String getColumnName(int col)
1485 return columnNames[col];
1489 public Object getValueAt(int row, int col)
1491 return data[row][col];
1495 * Answers the class of the object in column c of the first row of the table
1498 public Class<?> getColumnClass(int c)
1500 Object v = getValueAt(0, c);
1501 return v == null ? null : v.getClass();
1505 public boolean isCellEditable(int row, int col)
1507 return col == 0 ? false : true;
1511 public void setValueAt(Object value, int row, int col)
1513 data[row][col] = value;
1514 fireTableCellUpdated(row, col);
1515 updateFeatureRenderer(data);
1520 class ColorRenderer extends JLabel implements TableCellRenderer
1522 Border unselectedBorder = null;
1524 Border selectedBorder = null;
1526 public ColorRenderer()
1528 setOpaque(true); // MUST do this for background to show up.
1529 setHorizontalTextPosition(SwingConstants.CENTER);
1530 setVerticalTextPosition(SwingConstants.CENTER);
1534 public Component getTableCellRendererComponent(JTable tbl, Object color,
1535 boolean isSelected, boolean hasFocus, int row, int column)
1537 FeatureColourI cellColour = (FeatureColourI) color;
1539 setBackground(tbl.getBackground());
1540 if (!cellColour.isSimpleColour())
1542 Rectangle cr = tbl.getCellRect(row, column, false);
1543 FeatureSettings.renderGraduatedColor(this, cellColour,
1544 (int) cr.getWidth(), (int) cr.getHeight());
1550 setBackground(cellColour.getColour());
1554 if (selectedBorder == null)
1556 selectedBorder = BorderFactory.createMatteBorder(2, 5, 2, 5,
1557 tbl.getSelectionBackground());
1559 setBorder(selectedBorder);
1563 if (unselectedBorder == null)
1565 unselectedBorder = BorderFactory.createMatteBorder(2, 5, 2, 5,
1566 tbl.getBackground());
1568 setBorder(unselectedBorder);
1575 class FilterRenderer extends JLabel implements TableCellRenderer
1577 javax.swing.border.Border unselectedBorder = null;
1579 javax.swing.border.Border selectedBorder = null;
1581 public FilterRenderer()
1583 setOpaque(true); // MUST do this for background to show up.
1584 setHorizontalTextPosition(SwingConstants.CENTER);
1585 setVerticalTextPosition(SwingConstants.CENTER);
1589 public Component getTableCellRendererComponent(JTable tbl,
1590 Object filter, boolean isSelected, boolean hasFocus, int row,
1593 FeatureMatcherSetI theFilter = (FeatureMatcherSetI) filter;
1595 String asText = theFilter.toString();
1596 setBackground(tbl.getBackground());
1597 this.setText(asText);
1602 if (selectedBorder == null)
1604 selectedBorder = BorderFactory.createMatteBorder(2, 5, 2, 5,
1605 tbl.getSelectionBackground());
1607 setBorder(selectedBorder);
1611 if (unselectedBorder == null)
1613 unselectedBorder = BorderFactory.createMatteBorder(2, 5, 2, 5,
1614 tbl.getBackground());
1616 setBorder(unselectedBorder);
1624 * update comp using rendering settings from gcol
1629 public static void renderGraduatedColor(JLabel comp, FeatureColourI gcol)
1631 int w = comp.getWidth(), h = comp.getHeight();
1634 w = (int) comp.getPreferredSize().getWidth();
1635 h = (int) comp.getPreferredSize().getHeight();
1642 renderGraduatedColor(comp, gcol, w, h);
1645 class ColorEditor extends AbstractCellEditor
1646 implements TableCellEditor, ActionListener
1650 FeatureColourI currentColor;
1652 FeatureTypeSettings chooser;
1658 protected static final String EDIT = "edit";
1660 int rowSelected = 0;
1662 public ColorEditor(FeatureSettings fs)
1665 // Set up the editor (from the table's point of view),
1666 // which is a button.
1667 // This button brings up the color chooser dialog,
1668 // which is the editor from the user's point of view.
1669 button = new JButton();
1670 button.setActionCommand(EDIT);
1671 button.addActionListener(this);
1672 button.setBorderPainted(false);
1676 * Handles events from the editor button, and from the colour/filters
1677 * dialog's OK button
1680 public void actionPerformed(ActionEvent e)
1682 if (button == e.getSource())
1684 if (currentColor.isSimpleColour())
1687 * simple colour chooser
1689 String ttl = MessageManager.getString("label.select_colour");
1690 ColourChooserListener listener = new ColourChooserListener() {
1692 public void colourSelected(Color c)
1694 currentColor = new FeatureColour(c);
1695 me.table.setValueAt(currentColor, rowSelected, COLOUR_COLUMN);
1698 JalviewColourChooser.showColourChooser(button, ttl, currentColor.getColour(), listener);
1703 * variable colour and filters dialog
1705 chooser = new FeatureTypeSettings(me.fr, type);
1710 chooser.setRequestFocusEnabled(true);
1711 chooser.requestFocus();
1713 chooser.addActionListener(this);
1714 // Make the renderer reappear.
1715 fireEditingStopped();
1721 * after OK in variable colour dialog, any changes to colour
1722 * (or filters!) are already set in FeatureRenderer, so just
1723 * update table data without triggering updateFeatureRenderer
1725 currentColor = fr.getFeatureColours().get(type);
1726 FeatureMatcherSetI currentFilter = me.fr.getFeatureFilter(type);
1727 if (currentFilter == null)
1729 currentFilter = new FeatureMatcherSet();
1731 Object[] data = ((FeatureTableModel) table.getModel())
1732 .getData()[rowSelected];
1733 data[COLOUR_COLUMN] = currentColor;
1734 data[FILTER_COLUMN] = currentFilter;
1736 fireEditingStopped();
1737 me.table.validate();
1741 // Implement the one CellEditor method that AbstractCellEditor doesn't.
1743 public Object getCellEditorValue()
1745 return currentColor;
1748 // Implement the one method defined by TableCellEditor.
1750 public Component getTableCellEditorComponent(JTable theTable, Object value,
1751 boolean isSelected, int row, int column)
1753 currentColor = (FeatureColourI) value;
1754 this.rowSelected = row;
1755 type = me.table.getValueAt(row, TYPE_COLUMN).toString();
1756 button.setOpaque(true);
1757 button.setBackground(me.getBackground());
1758 if (!currentColor.isSimpleColour())
1760 JLabel btn = new JLabel();
1761 btn.setSize(button.getSize());
1762 FeatureSettings.renderGraduatedColor(btn, currentColor);
1763 button.setBackground(btn.getBackground());
1764 button.setIcon(btn.getIcon());
1765 button.setText(btn.getText());
1770 button.setIcon(null);
1771 button.setBackground(currentColor.getColour());
1778 * The cell editor for the Filter column. It displays the text of any filters
1779 * for the feature type in that row (in full as a tooltip, possible abbreviated
1780 * as display text). On click in the cell, opens the Feature Display Settings
1781 * dialog at the Filters tab.
1783 class FilterEditor extends AbstractCellEditor
1784 implements TableCellEditor, ActionListener
1788 FeatureMatcherSetI currentFilter;
1796 protected static final String EDIT = "edit";
1798 int rowSelected = 0;
1800 public FilterEditor(FeatureSettings me)
1803 button = new JButton();
1804 button.setActionCommand(EDIT);
1805 button.addActionListener(this);
1806 button.setBorderPainted(false);
1810 * Handles events from the editor button
1813 public void actionPerformed(ActionEvent e)
1815 if (button == e.getSource())
1817 FeatureTypeSettings chooser = new FeatureTypeSettings(me.fr, type);
1818 chooser.addActionListener(this);
1819 chooser.setRequestFocusEnabled(true);
1820 chooser.requestFocus();
1821 if (lastLocation != null)
1823 // todo open at its last position on screen
1824 chooser.setBounds(lastLocation.x, lastLocation.y,
1825 chooser.getWidth(), chooser.getHeight());
1828 fireEditingStopped();
1830 else if (e.getSource() instanceof Component)
1834 * after OK in variable colour dialog, any changes to filter
1835 * (or colours!) are already set in FeatureRenderer, so just
1836 * update table data without triggering updateFeatureRenderer
1838 FeatureColourI currentColor = fr.getFeatureColours().get(type);
1839 currentFilter = me.fr.getFeatureFilter(type);
1840 if (currentFilter == null)
1842 currentFilter = new FeatureMatcherSet();
1844 Object[] data = ((FeatureTableModel) table.getModel())
1845 .getData()[rowSelected];
1846 data[COLOUR_COLUMN] = currentColor;
1847 data[FILTER_COLUMN] = currentFilter;
1848 fireEditingStopped();
1849 me.table.validate();
1854 public Object getCellEditorValue()
1856 return currentFilter;
1860 public Component getTableCellEditorComponent(JTable theTable, Object value,
1861 boolean isSelected, int row, int column)
1863 currentFilter = (FeatureMatcherSetI) value;
1864 this.rowSelected = row;
1865 type = me.table.getValueAt(row, TYPE_COLUMN).toString();
1866 button.setOpaque(true);
1867 button.setBackground(me.getBackground());
1868 button.setText(currentFilter.toString());
1869 button.setIcon(null);
1875 class FeatureIcon implements Icon
1877 FeatureColourI gcol;
1881 boolean midspace = false;
1883 int width = 50, height = 20;
1885 int s1, e1; // start and end of midpoint band for thresholded symbol
1887 Color mpcolour = Color.white;
1889 FeatureIcon(FeatureColourI gfc, Color bg, int w, int h, boolean mspace)
1909 public int getIconWidth()
1915 public int getIconHeight()
1921 public void paintIcon(Component c, Graphics g, int x, int y)
1924 if (gcol.isColourByLabel())
1927 g.fillRect(0, 0, width, height);
1928 // need an icon here.
1929 g.setColor(gcol.getMaxColour());
1931 g.setFont(new Font("Verdana", Font.PLAIN, 9));
1933 // g.setFont(g.getFont().deriveFont(
1934 // AffineTransform.getScaleInstance(
1935 // width/g.getFontMetrics().stringWidth("Label"),
1936 // height/g.getFontMetrics().getHeight())));
1938 g.drawString(MessageManager.getString("label.label"), 0, 0);
1943 Color minCol = gcol.getMinColour();
1945 g.fillRect(0, 0, s1, height);
1948 g.setColor(Color.white);
1949 g.fillRect(s1, 0, e1 - s1, height);
1951 g.setColor(gcol.getMaxColour());
1952 g.fillRect(0, e1, width - e1, height);