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.bin.Jalview;
26 import jalview.datamodel.AlignmentI;
27 import jalview.datamodel.SequenceI;
28 import jalview.datamodel.features.FeatureMatcher;
29 import jalview.datamodel.features.FeatureMatcherI;
30 import jalview.datamodel.features.FeatureMatcherSet;
31 import jalview.datamodel.features.FeatureMatcherSetI;
32 import jalview.gui.Help.HelpId;
33 import jalview.gui.JalviewColourChooser.ColourChooserListener;
34 import jalview.io.JalviewFileChooser;
35 import jalview.io.JalviewFileView;
36 import jalview.schemabinding.version2.Filter;
37 import jalview.schemabinding.version2.JalviewUserColours;
38 import jalview.schemabinding.version2.MatcherSet;
39 import jalview.schemes.FeatureColour;
40 import jalview.util.MessageManager;
41 import jalview.util.Platform;
42 import jalview.viewmodel.seqfeatures.FeatureRendererModel.FeatureSettingsBean;
44 import java.awt.BorderLayout;
45 import java.awt.Color;
46 import java.awt.Component;
47 import java.awt.Dimension;
48 import java.awt.FlowLayout;
50 import java.awt.Graphics;
51 import java.awt.GridLayout;
52 import java.awt.Point;
53 import java.awt.Rectangle;
54 import java.awt.event.ActionEvent;
55 import java.awt.event.ActionListener;
56 import java.awt.event.ItemEvent;
57 import java.awt.event.ItemListener;
58 import java.awt.event.MouseAdapter;
59 import java.awt.event.MouseEvent;
60 import java.awt.event.MouseMotionAdapter;
61 import java.beans.PropertyChangeEvent;
62 import java.beans.PropertyChangeListener;
64 import java.io.FileInputStream;
65 import java.io.FileOutputStream;
66 import java.io.InputStreamReader;
67 import java.io.OutputStreamWriter;
68 import java.io.PrintWriter;
69 import java.util.Arrays;
70 import java.util.Comparator;
71 import java.util.HashMap;
72 import java.util.HashSet;
73 import java.util.Hashtable;
74 import java.util.Iterator;
75 import java.util.List;
79 import javax.help.HelpSetException;
80 import javax.swing.AbstractCellEditor;
81 import javax.swing.BorderFactory;
82 import javax.swing.Icon;
83 import javax.swing.JButton;
84 import javax.swing.JCheckBox;
85 import javax.swing.JCheckBoxMenuItem;
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, true);
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 if (!Jalview.isJS())
1356 * no save/load XML in JalviewJS for now
1358 buttonPanel.add(loadColours);
1359 buttonPanel.add(saveColours);
1361 bigPanel.add(scrollPane, BorderLayout.CENTER);
1362 settingsPane.add(bigPanel, BorderLayout.CENTER);
1363 settingsPane.add(buttonPanel, BorderLayout.SOUTH);
1364 this.add(settingsPane);
1368 * Answers a suitable tooltip to show on the colour cell of the table
1372 * if true include 'click to edit' and similar text
1375 public static String getColorTooltip(FeatureColourI fcol,
1382 if (fcol.isSimpleColour())
1384 return withHint ? BASE_TOOLTIP : null;
1386 String description = fcol.getDescription();
1387 description = description.replaceAll("<", "<");
1388 description = description.replaceAll(">", ">");
1389 StringBuilder tt = new StringBuilder(description);
1392 tt.append("<br>").append(BASE_TOOLTIP).append("</br>");
1394 return JvSwingUtils.wrapTooltip(true, tt.toString());
1397 public static void renderGraduatedColor(JLabel comp, FeatureColourI gcol,
1400 boolean thr = false;
1401 StringBuilder tx = new StringBuilder();
1403 if (gcol.isColourByAttribute())
1405 tx.append(FeatureMatcher
1406 .toAttributeDisplayName(gcol.getAttributeName()));
1408 else if (!gcol.isColourByLabel())
1410 tx.append(MessageManager.getString("label.score"));
1413 if (gcol.isAboveThreshold())
1418 if (gcol.isBelowThreshold())
1423 if (gcol.isColourByLabel())
1429 if (!gcol.isColourByAttribute())
1437 Color newColor = gcol.getMaxColour();
1438 comp.setBackground(newColor);
1439 // System.err.println("Width is " + w / 2);
1440 Icon ficon = new FeatureIcon(gcol, comp.getBackground(), w, h, thr);
1441 comp.setIcon(ficon);
1442 // tt+="RGB value: Max (" + newColor.getRed() + ", "
1443 // + newColor.getGreen() + ", " + newColor.getBlue()
1444 // + ")\nMin (" + minCol.getRed() + ", " + minCol.getGreen()
1445 // + ", " + minCol.getBlue() + ")");
1447 comp.setHorizontalAlignment(SwingConstants.CENTER);
1448 comp.setText(tx.toString());
1451 // ///////////////////////////////////////////////////////////////////////
1452 // http://java.sun.com/docs/books/tutorial/uiswing/components/table.html
1453 // ///////////////////////////////////////////////////////////////////////
1454 class FeatureTableModel extends AbstractTableModel
1456 private String[] columnNames = {
1457 MessageManager.getString("label.feature_type"),
1458 MessageManager.getString("action.colour"),
1459 MessageManager.getString("label.filter"),
1460 MessageManager.getString("label.show") };
1462 private Object[][] data;
1464 FeatureTableModel(Object[][] data)
1469 public Object[][] getData()
1474 public void setData(Object[][] data)
1480 public int getColumnCount()
1482 return columnNames.length;
1485 public Object[] getRow(int row)
1491 public int getRowCount()
1497 public String getColumnName(int col)
1499 return columnNames[col];
1503 public Object getValueAt(int row, int col)
1505 return data[row][col];
1509 * Answers the class of the object in column c of the first row of the table
1512 public Class<?> getColumnClass(int c)
1514 Object v = getValueAt(0, c);
1515 return v == null ? null : v.getClass();
1519 public boolean isCellEditable(int row, int col)
1521 return col == 0 ? false : true;
1525 public void setValueAt(Object value, int row, int col)
1527 data[row][col] = value;
1528 fireTableCellUpdated(row, col);
1529 updateFeatureRenderer(data);
1534 class ColorRenderer extends JLabel implements TableCellRenderer
1536 Border unselectedBorder = null;
1538 Border selectedBorder = null;
1540 public ColorRenderer()
1542 setOpaque(true); // MUST do this for background to show up.
1543 setHorizontalTextPosition(SwingConstants.CENTER);
1544 setVerticalTextPosition(SwingConstants.CENTER);
1548 public Component getTableCellRendererComponent(JTable tbl, Object color,
1549 boolean isSelected, boolean hasFocus, int row, int column)
1551 FeatureColourI cellColour = (FeatureColourI) color;
1553 setBackground(tbl.getBackground());
1554 if (!cellColour.isSimpleColour())
1556 Rectangle cr = tbl.getCellRect(row, column, false);
1557 FeatureSettings.renderGraduatedColor(this, cellColour,
1558 (int) cr.getWidth(), (int) cr.getHeight());
1564 setBackground(cellColour.getColour());
1568 if (selectedBorder == null)
1570 selectedBorder = BorderFactory.createMatteBorder(2, 5, 2, 5,
1571 tbl.getSelectionBackground());
1573 setBorder(selectedBorder);
1577 if (unselectedBorder == null)
1579 unselectedBorder = BorderFactory.createMatteBorder(2, 5, 2, 5,
1580 tbl.getBackground());
1582 setBorder(unselectedBorder);
1589 class FilterRenderer extends JLabel implements TableCellRenderer
1591 javax.swing.border.Border unselectedBorder = null;
1593 javax.swing.border.Border selectedBorder = null;
1595 public FilterRenderer()
1597 setOpaque(true); // MUST do this for background to show up.
1598 setHorizontalTextPosition(SwingConstants.CENTER);
1599 setVerticalTextPosition(SwingConstants.CENTER);
1603 public Component getTableCellRendererComponent(JTable tbl,
1604 Object filter, boolean isSelected, boolean hasFocus, int row,
1607 FeatureMatcherSetI theFilter = (FeatureMatcherSetI) filter;
1609 String asText = theFilter.toString();
1610 setBackground(tbl.getBackground());
1611 this.setText(asText);
1616 if (selectedBorder == null)
1618 selectedBorder = BorderFactory.createMatteBorder(2, 5, 2, 5,
1619 tbl.getSelectionBackground());
1621 setBorder(selectedBorder);
1625 if (unselectedBorder == null)
1627 unselectedBorder = BorderFactory.createMatteBorder(2, 5, 2, 5,
1628 tbl.getBackground());
1630 setBorder(unselectedBorder);
1638 * update comp using rendering settings from gcol
1643 public static void renderGraduatedColor(JLabel comp, FeatureColourI gcol)
1645 int w = comp.getWidth(), h = comp.getHeight();
1648 w = (int) comp.getPreferredSize().getWidth();
1649 h = (int) comp.getPreferredSize().getHeight();
1656 renderGraduatedColor(comp, gcol, w, h);
1659 class ColorEditor extends AbstractCellEditor
1660 implements TableCellEditor, ActionListener
1664 FeatureColourI currentColor;
1666 FeatureTypeSettings chooser;
1672 protected static final String EDIT = "edit";
1674 int rowSelected = 0;
1676 public ColorEditor(FeatureSettings fs)
1679 // Set up the editor (from the table's point of view),
1680 // which is a button.
1681 // This button brings up the color chooser dialog,
1682 // which is the editor from the user's point of view.
1683 button = new JButton();
1684 button.setActionCommand(EDIT);
1685 button.addActionListener(this);
1686 button.setBorderPainted(false);
1690 * Handles events from the editor button, and from the colour/filters
1691 * dialog's OK button
1694 public void actionPerformed(ActionEvent e)
1696 if (button == e.getSource())
1698 if (currentColor.isSimpleColour())
1701 * simple colour chooser
1703 String ttl = MessageManager.getString("label.select_colour");
1704 ColourChooserListener listener = new ColourChooserListener() {
1706 public void colourSelected(Color c)
1708 currentColor = new FeatureColour(c);
1709 me.table.setValueAt(currentColor, rowSelected, COLOUR_COLUMN);
1712 JalviewColourChooser.showColourChooser(button, ttl, currentColor.getColour(), listener);
1717 * variable colour and filters dialog
1719 chooser = new FeatureTypeSettings(me.fr, type);
1724 chooser.setRequestFocusEnabled(true);
1725 chooser.requestFocus();
1727 chooser.addActionListener(this);
1728 // Make the renderer reappear.
1729 fireEditingStopped();
1735 * after OK in variable colour dialog, any changes to colour
1736 * (or filters!) are already set in FeatureRenderer, so just
1737 * update table data without triggering updateFeatureRenderer
1739 currentColor = fr.getFeatureColours().get(type);
1740 FeatureMatcherSetI currentFilter = me.fr.getFeatureFilter(type);
1741 if (currentFilter == null)
1743 currentFilter = new FeatureMatcherSet();
1745 Object[] data = ((FeatureTableModel) table.getModel())
1746 .getData()[rowSelected];
1747 data[COLOUR_COLUMN] = currentColor;
1748 data[FILTER_COLUMN] = currentFilter;
1750 fireEditingStopped();
1751 me.table.validate();
1755 // Implement the one CellEditor method that AbstractCellEditor doesn't.
1757 public Object getCellEditorValue()
1759 return currentColor;
1762 // Implement the one method defined by TableCellEditor.
1764 public Component getTableCellEditorComponent(JTable theTable, Object value,
1765 boolean isSelected, int row, int column)
1767 currentColor = (FeatureColourI) value;
1768 this.rowSelected = row;
1769 type = me.table.getValueAt(row, TYPE_COLUMN).toString();
1770 button.setOpaque(true);
1771 button.setBackground(me.getBackground());
1772 if (!currentColor.isSimpleColour())
1774 JLabel btn = new JLabel();
1775 btn.setSize(button.getSize());
1776 FeatureSettings.renderGraduatedColor(btn, currentColor);
1777 button.setBackground(btn.getBackground());
1778 button.setIcon(btn.getIcon());
1779 button.setText(btn.getText());
1784 button.setIcon(null);
1785 button.setBackground(currentColor.getColour());
1792 * The cell editor for the Filter column. It displays the text of any filters
1793 * for the feature type in that row (in full as a tooltip, possible abbreviated
1794 * as display text). On click in the cell, opens the Feature Display Settings
1795 * dialog at the Filters tab.
1797 class FilterEditor extends AbstractCellEditor
1798 implements TableCellEditor, ActionListener
1802 FeatureMatcherSetI currentFilter;
1810 protected static final String EDIT = "edit";
1812 int rowSelected = 0;
1814 public FilterEditor(FeatureSettings me)
1817 button = new JButton();
1818 button.setActionCommand(EDIT);
1819 button.addActionListener(this);
1820 button.setBorderPainted(false);
1824 * Handles events from the editor button
1827 public void actionPerformed(ActionEvent e)
1829 if (button == e.getSource())
1831 FeatureTypeSettings chooser = new FeatureTypeSettings(me.fr, type);
1832 chooser.addActionListener(this);
1833 chooser.setRequestFocusEnabled(true);
1834 chooser.requestFocus();
1835 if (lastLocation != null)
1837 // todo open at its last position on screen
1838 chooser.setBounds(lastLocation.x, lastLocation.y,
1839 chooser.getWidth(), chooser.getHeight());
1842 fireEditingStopped();
1844 else if (e.getSource() instanceof Component)
1848 * after OK in variable colour dialog, any changes to filter
1849 * (or colours!) are already set in FeatureRenderer, so just
1850 * update table data without triggering updateFeatureRenderer
1852 FeatureColourI currentColor = fr.getFeatureColours().get(type);
1853 currentFilter = me.fr.getFeatureFilter(type);
1854 if (currentFilter == null)
1856 currentFilter = new FeatureMatcherSet();
1858 Object[] data = ((FeatureTableModel) table.getModel())
1859 .getData()[rowSelected];
1860 data[COLOUR_COLUMN] = currentColor;
1861 data[FILTER_COLUMN] = currentFilter;
1862 fireEditingStopped();
1863 me.table.validate();
1868 public Object getCellEditorValue()
1870 return currentFilter;
1874 public Component getTableCellEditorComponent(JTable theTable, Object value,
1875 boolean isSelected, int row, int column)
1877 currentFilter = (FeatureMatcherSetI) value;
1878 this.rowSelected = row;
1879 type = me.table.getValueAt(row, TYPE_COLUMN).toString();
1880 button.setOpaque(true);
1881 button.setBackground(me.getBackground());
1882 button.setText(currentFilter.toString());
1883 button.setIcon(null);
1889 class FeatureIcon implements Icon
1891 FeatureColourI gcol;
1895 boolean midspace = false;
1897 int width = 50, height = 20;
1899 int s1, e1; // start and end of midpoint band for thresholded symbol
1901 Color mpcolour = Color.white;
1903 FeatureIcon(FeatureColourI gfc, Color bg, int w, int h, boolean mspace)
1923 public int getIconWidth()
1929 public int getIconHeight()
1935 public void paintIcon(Component c, Graphics g, int x, int y)
1938 if (gcol.isColourByLabel())
1941 g.fillRect(0, 0, width, height);
1942 // need an icon here.
1943 g.setColor(gcol.getMaxColour());
1945 g.setFont(new Font("Verdana", Font.PLAIN, 9));
1947 // g.setFont(g.getFont().deriveFont(
1948 // AffineTransform.getScaleInstance(
1949 // width/g.getFontMetrics().stringWidth("Label"),
1950 // height/g.getFontMetrics().getHeight())));
1952 g.drawString(MessageManager.getString("label.label"), 0, 0);
1957 Color minCol = gcol.getMinColour();
1959 g.fillRect(0, 0, s1, height);
1962 g.setColor(Color.white);
1963 g.fillRect(s1, 0, e1 - s1, height);
1965 g.setColor(gcol.getMaxColour());
1966 g.fillRect(0, e1, width - e1, height);