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 = MessageManager.getString("label.click_to_edit");
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();
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")
239 * Position the tooltip near 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 - 3);
253 table.getTableHeader().setFont(new Font("Verdana", Font.PLAIN, 12));
254 ToolTipManager.sharedInstance().registerComponent(table);
256 table.setDefaultEditor(FeatureColour.class, new ColorEditor(this));
257 table.setDefaultRenderer(FeatureColour.class, new ColorRenderer());
259 table.setDefaultEditor(FeatureMatcherSet.class, new FilterEditor(this));
260 table.setDefaultRenderer(FeatureMatcherSet.class, new FilterRenderer());
262 TableColumn colourColumn = new TableColumn(COLOUR_COLUMN, 75,
263 new ColorRenderer(), new ColorEditor(this));
264 table.addColumn(colourColumn);
266 TableColumn filterColumn = new TableColumn(FILTER_COLUMN, 75,
267 new FilterRenderer(), new FilterEditor(this));
268 table.addColumn(filterColumn);
270 table.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
272 table.addMouseListener(new MouseAdapter()
275 public void mousePressed(MouseEvent evt)
277 selectedRow = table.rowAtPoint(evt.getPoint());
278 String type = (String) table.getValueAt(selectedRow, TYPE_COLUMN);
279 if (evt.isPopupTrigger())
281 Object colour = table.getValueAt(selectedRow, COLOUR_COLUMN);
282 popupSort(selectedRow, type, colour, fr.getMinMax(), evt.getX(),
285 else if (evt.getClickCount() == 2)
287 boolean invertSelection = evt.isAltDown();
288 boolean toggleSelection = Platform.isControlDown(evt);
289 boolean extendSelection = evt.isShiftDown();
290 fr.ap.alignFrame.avc.markColumnsContainingFeatures(
291 invertSelection, extendSelection, toggleSelection, type);
295 // isPopupTrigger fires on mouseReleased on Windows
297 public void mouseReleased(MouseEvent evt)
299 selectedRow = table.rowAtPoint(evt.getPoint());
300 if (evt.isPopupTrigger())
302 String type = (String) table.getValueAt(selectedRow, TYPE_COLUMN);
303 Object colour = table.getValueAt(selectedRow, COLOUR_COLUMN);
304 popupSort(selectedRow, type, colour, fr.getMinMax(), evt.getX(),
310 table.addMouseMotionListener(new MouseMotionAdapter()
313 public void mouseDragged(MouseEvent evt)
315 int newRow = table.rowAtPoint(evt.getPoint());
316 if (newRow != selectedRow && selectedRow != -1 && newRow != -1)
319 * reposition 'selectedRow' to 'newRow' (the dragged to location)
320 * this could be more than one row away for a very fast drag action
321 * so just swap it with adjacent rows until we get it there
323 Object[][] data = ((FeatureTableModel) table.getModel())
325 int direction = newRow < selectedRow ? -1 : 1;
326 for (int i = selectedRow; i != newRow; i += direction)
328 Object[] temp = data[i];
329 data[i] = data[i + direction];
330 data[i + direction] = temp;
332 updateFeatureRenderer(data);
334 selectedRow = newRow;
338 // table.setToolTipText(JvSwingUtils.wrapTooltip(true,
339 // MessageManager.getString("label.feature_settings_click_drag")));
340 scrollPane.setViewportView(table);
342 if (af.getViewport().isShowSequenceFeatures() || !fr.hasRenderOrder())
344 fr.findAllFeatures(true); // display everything!
347 discoverAllFeatureData();
348 final PropertyChangeListener change;
349 final FeatureSettings fs = this;
350 fr.addPropertyChangeListener(change = new PropertyChangeListener()
353 public void propertyChange(PropertyChangeEvent evt)
355 if (!fs.resettingTable && !fs.handlingUpdate)
357 fs.handlingUpdate = true;
359 // new groups may be added with new sequence feature types only
360 fs.handlingUpdate = false;
366 frame = new JInternalFrame();
367 frame.setContentPane(this);
368 if (Platform.isAMac())
370 Desktop.addInternalFrame(frame,
371 MessageManager.getString("label.sequence_feature_settings"),
376 Desktop.addInternalFrame(frame,
377 MessageManager.getString("label.sequence_feature_settings"),
380 frame.setMinimumSize(new Dimension(MIN_WIDTH, MIN_HEIGHT));
382 frame.addInternalFrameListener(
383 new javax.swing.event.InternalFrameAdapter()
386 public void internalFrameClosed(
387 javax.swing.event.InternalFrameEvent evt)
389 fr.removePropertyChangeListener(change);
392 frame.setLayer(JLayeredPane.PALETTE_LAYER);
393 inConstruction = false;
396 protected void popupSort(final int rowSelected, final String type,
397 final Object typeCol, final Map<String, float[][]> minmax, int x,
400 final FeatureColourI featureColour = (FeatureColourI) typeCol;
402 JPopupMenu men = new JPopupMenu(MessageManager
403 .formatMessage("label.settings_for_param", new String[]
405 JMenuItem scr = new JMenuItem(
406 MessageManager.getString("label.sort_by_score"));
408 final FeatureSettings me = this;
409 scr.addActionListener(new ActionListener()
413 public void actionPerformed(ActionEvent e)
416 .sortAlignmentByFeatureScore(Arrays.asList(new String[]
421 JMenuItem dens = new JMenuItem(
422 MessageManager.getString("label.sort_by_density"));
423 dens.addActionListener(new ActionListener()
427 public void actionPerformed(ActionEvent e)
430 .sortAlignmentByFeatureDensity(Arrays.asList(new String[]
438 * variable colour options include colour by label, by score,
439 * by selected attribute text, or attribute value
441 final JCheckBoxMenuItem variableColourCB = new JCheckBoxMenuItem(
442 MessageManager.getString("label.variable_colour"));
443 variableColourCB.setSelected(!featureColour.isSimpleColour());
444 men.add(variableColourCB);
447 * checkbox action listener doubles up as listener to OK
448 * from the variable colour / filters dialog
450 variableColourCB.addActionListener(new ActionListener()
453 public void actionPerformed(ActionEvent e)
455 if (e.getSource() == variableColourCB)
457 if (featureColour.isSimpleColour())
460 * toggle simple colour to variable colour - show dialog
462 FeatureTypeSettings fc = new FeatureTypeSettings(me.fr, type);
463 fc.addActionListener(this);
468 * toggle variable to simple colour - show colour chooser
470 String title = MessageManager.getString("label.select_colour");
471 ColourChooserListener listener = new ColourChooserListener()
474 public void colourSelected(Color c)
476 table.setValueAt(new FeatureColour(c), rowSelected,
479 me.updateFeatureRenderer(
480 ((FeatureTableModel) table.getModel()).getData(),
484 JalviewColourChooser.showColourChooser(me, title, featureColour.getMaxColour(), listener);
488 if (e.getSource() instanceof FeatureTypeSettings)
491 * update after OK in feature colour dialog; the updated
492 * colour will have already been set in the FeatureRenderer
494 FeatureColourI fci = fr.getFeatureColours().get(type);
495 table.setValueAt(fci, rowSelected, COLOUR_COLUMN);
503 JMenuItem selCols = new JMenuItem(
504 MessageManager.getString("label.select_columns_containing"));
505 selCols.addActionListener(new ActionListener()
508 public void actionPerformed(ActionEvent arg0)
510 fr.ap.alignFrame.avc.markColumnsContainingFeatures(false, false,
514 JMenuItem clearCols = new JMenuItem(MessageManager
515 .getString("label.select_columns_not_containing"));
516 clearCols.addActionListener(new ActionListener()
519 public void actionPerformed(ActionEvent arg0)
521 fr.ap.alignFrame.avc.markColumnsContainingFeatures(true, false,
525 JMenuItem hideCols = new JMenuItem(
526 MessageManager.getString("label.hide_columns_containing"));
527 hideCols.addActionListener(new ActionListener()
530 public void actionPerformed(ActionEvent arg0)
532 fr.ap.alignFrame.hideFeatureColumns(type, true);
535 JMenuItem hideOtherCols = new JMenuItem(
536 MessageManager.getString("label.hide_columns_not_containing"));
537 hideOtherCols.addActionListener(new ActionListener()
540 public void actionPerformed(ActionEvent arg0)
542 fr.ap.alignFrame.hideFeatureColumns(type, false);
548 men.add(hideOtherCols);
549 men.show(table, x, y);
553 synchronized public void discoverAllFeatureData()
555 Set<String> allGroups = new HashSet<>();
556 AlignmentI alignment = af.getViewport().getAlignment();
558 for (int i = 0; i < alignment.getHeight(); i++)
560 SequenceI seq = alignment.getSequenceAt(i);
561 for (String group : seq.getFeatures().getFeatureGroups(true))
563 if (group != null && !allGroups.contains(group))
565 allGroups.add(group);
566 checkGroupState(group);
577 * Synchronise gui group list and check visibility of group
580 * @return true if group is visible
582 private boolean checkGroupState(String group)
584 boolean visible = fr.checkGroupVisibility(group, true);
586 for (int g = 0; g < groupPanel.getComponentCount(); g++)
588 if (((JCheckBox) groupPanel.getComponent(g)).getText().equals(group))
590 ((JCheckBox) groupPanel.getComponent(g)).setSelected(visible);
595 final String grp = group;
596 final JCheckBox check = new JCheckBox(group, visible);
597 check.setFont(new Font("Serif", Font.BOLD, 12));
598 check.setToolTipText(group);
599 check.addItemListener(new ItemListener()
602 public void itemStateChanged(ItemEvent evt)
604 fr.setGroupVisibility(check.getText(), check.isSelected());
605 resetTable(new String[] { grp });
606 af.alignPanel.paintAlignment(true, true);
609 groupPanel.add(check);
613 synchronized void resetTable(String[] groupChanged)
619 resettingTable = true;
620 typeWidth = new Hashtable<>();
621 // TODO: change avWidth calculation to 'per-sequence' average and use long
624 Set<String> displayableTypes = new HashSet<>();
625 Set<String> foundGroups = new HashSet<>();
628 * determine which feature types may be visible depending on
629 * which groups are selected, and recompute average width data
631 for (int i = 0; i < af.getViewport().getAlignment().getHeight(); i++)
634 SequenceI seq = af.getViewport().getAlignment().getSequenceAt(i);
637 * get the sequence's groups for positional features
638 * and keep track of which groups are visible
640 Set<String> groups = seq.getFeatures().getFeatureGroups(true);
641 Set<String> visibleGroups = new HashSet<>();
642 for (String group : groups)
644 if (group == null || checkGroupState(group))
646 visibleGroups.add(group);
649 foundGroups.addAll(groups);
652 * get distinct feature types for visible groups
653 * record distinct visible types, and their count and total length
655 Set<String> types = seq.getFeatures().getFeatureTypesForGroups(true,
656 visibleGroups.toArray(new String[visibleGroups.size()]));
657 for (String type : types)
659 displayableTypes.add(type);
660 float[] avWidth = typeWidth.get(type);
663 avWidth = new float[2];
664 typeWidth.put(type, avWidth);
666 // todo this could include features with a non-visible group
667 // - do we greatly care?
668 // todo should we include non-displayable features here, and only
669 // update when features are added?
670 avWidth[0] += seq.getFeatures().getFeatureCount(true, type);
671 avWidth[1] += seq.getFeatures().getTotalFeatureLength(type);
675 Object[][] data = new Object[displayableTypes.size()][COLUMN_COUNT];
678 if (fr.hasRenderOrder())
682 fr.findAllFeatures(groupChanged != null); // prod to update
683 // colourschemes. but don't
685 // First add the checks in the previous render order,
686 // in case the window has been closed and reopened
688 List<String> frl = fr.getRenderOrder();
689 for (int ro = frl.size() - 1; ro > -1; ro--)
691 String type = frl.get(ro);
693 if (!displayableTypes.contains(type))
698 data[dataIndex][TYPE_COLUMN] = type;
699 data[dataIndex][COLOUR_COLUMN] = fr.getFeatureStyle(type);
700 FeatureMatcherSetI featureFilter = fr.getFeatureFilter(type);
701 data[dataIndex][FILTER_COLUMN] = featureFilter == null
702 ? new FeatureMatcherSet()
704 data[dataIndex][SHOW_COLUMN] = new Boolean(
705 af.getViewport().getFeaturesDisplayed().isVisible(type));
707 displayableTypes.remove(type);
712 * process any extra features belonging only to
713 * a group which was just selected
715 while (!displayableTypes.isEmpty())
717 String type = displayableTypes.iterator().next();
718 data[dataIndex][TYPE_COLUMN] = type;
720 data[dataIndex][COLOUR_COLUMN] = fr.getFeatureStyle(type);
721 if (data[dataIndex][COLOUR_COLUMN] == null)
723 // "Colour has been updated in another view!!"
724 fr.clearRenderOrder();
727 FeatureMatcherSetI featureFilter = fr.getFeatureFilter(type);
728 data[dataIndex][FILTER_COLUMN] = featureFilter == null
729 ? new FeatureMatcherSet()
731 data[dataIndex][SHOW_COLUMN] = new Boolean(true);
733 displayableTypes.remove(type);
736 if (originalData == null)
738 originalData = new Object[data.length][COLUMN_COUNT];
739 for (int i = 0; i < data.length; i++)
741 System.arraycopy(data[i], 0, originalData[i], 0, COLUMN_COUNT);
746 updateOriginalData(data);
749 table.setModel(new FeatureTableModel(data));
750 table.getColumnModel().getColumn(0).setPreferredWidth(200);
752 groupPanel.setLayout(
753 new GridLayout(fr.getFeatureGroupsSize() / 4 + 1, 4));
754 pruneGroups(foundGroups);
755 groupPanel.validate();
757 updateFeatureRenderer(data, groupChanged != null);
758 resettingTable = false;
762 * Updates 'originalData' (used for restore on Cancel) if we detect that changes
763 * have been made outwith this dialog
765 * <li>a new feature type added (and made visible)</li>
766 * <li>a feature colour changed (in the Amend Features dialog)</li>
771 protected void updateOriginalData(Object[][] foundData)
773 // todo LinkedHashMap instead of Object[][] would be nice
775 Object[][] currentData = ((FeatureTableModel) table.getModel())
777 for (Object[] row : foundData)
779 String type = (String) row[TYPE_COLUMN];
780 boolean found = false;
781 for (Object[] current : currentData)
783 if (type.equals(current[TYPE_COLUMN]))
787 * currently dependent on object equality here;
788 * really need an equals method on FeatureColour
790 if (!row[COLOUR_COLUMN].equals(current[COLOUR_COLUMN]))
793 * feature colour has changed externally - update originalData
795 for (Object[] original : originalData)
797 if (type.equals(original[TYPE_COLUMN]))
799 original[COLOUR_COLUMN] = row[COLOUR_COLUMN];
810 * new feature detected - add to original data (on top)
812 Object[][] newData = new Object[originalData.length
814 for (int i = 0; i < originalData.length; i++)
816 System.arraycopy(originalData[i], 0, newData[i + 1], 0,
820 originalData = newData;
826 * Remove from the groups panel any checkboxes for groups that are not in the
827 * foundGroups set. This enables removing a group from the display when the last
828 * feature in that group is deleted.
832 protected void pruneGroups(Set<String> foundGroups)
834 for (int g = 0; g < groupPanel.getComponentCount(); g++)
836 JCheckBox checkbox = (JCheckBox) groupPanel.getComponent(g);
837 if (!foundGroups.contains(checkbox.getText()))
839 groupPanel.remove(checkbox);
845 * reorder data based on the featureRenderers global priority list.
849 private void ensureOrder(Object[][] data)
851 boolean sort = false;
852 float[] order = new float[data.length];
853 for (int i = 0; i < order.length; i++)
855 order[i] = fr.getOrder(data[i][0].toString());
858 order[i] = fr.setOrder(data[i][0].toString(), i / order.length);
862 sort = sort || order[i - 1] > order[i];
867 jalview.util.QuickSort.sort(order, data);
872 * Offers a file chooser dialog, and then loads the feature colours and
873 * filters from file in XML format and unmarshals to Jalview feature settings
877 // TODO: JAL-3048 relies on Castor XML parsing: not needed for JS-jalview core
880 JalviewFileChooser chooser = new JalviewFileChooser("fc",
881 SEQUENCE_FEATURE_COLOURS);
882 chooser.setFileView(new JalviewFileView());
883 chooser.setDialogTitle(
884 MessageManager.getString("label.load_feature_colours"));
885 chooser.setToolTipText(MessageManager.getString("action.load"));
887 int value = chooser.showOpenDialog(this);
889 if (value == JalviewFileChooser.APPROVE_OPTION)
891 File file = chooser.getSelectedFile();
897 * Loads feature colours and filters from XML stored in the given file
905 InputStreamReader in = new InputStreamReader(
906 new FileInputStream(file), "UTF-8");
908 JalviewUserColours jucs = JalviewUserColours.unmarshal(in);
911 * load feature colours
913 for (int i = jucs.getColourCount() - 1; i >= 0; i--)
915 jalview.schemabinding.version2.Colour newcol = jucs.getColour(i);
916 FeatureColourI colour = Jalview2XML.unmarshalColour(newcol);
917 fr.setColour(newcol.getName(), colour);
918 fr.setOrder(newcol.getName(), i / (float) jucs.getColourCount());
922 * load feature filters; loaded filters will replace any that are
923 * currently defined, other defined filters are left unchanged
925 for (int i = 0; i < jucs.getFilterCount(); i++)
927 jalview.schemabinding.version2.Filter filterModel = jucs
929 String featureType = filterModel.getFeatureType();
930 FeatureMatcherSetI filter = Jalview2XML.unmarshalFilter(featureType,
931 filterModel.getMatcherSet());
932 if (!filter.isEmpty())
934 fr.setFeatureFilter(featureType, filter);
939 * update feature settings table
944 Object[][] data = ((FeatureTableModel) table.getModel())
947 updateFeatureRenderer(data, false);
950 } catch (Exception ex)
952 System.out.println("Error loading User Colour File\n" + ex);
957 * Offers a file chooser dialog, and then saves the current feature colours
958 * and any filters to the selected file in XML format
962 // TODO: JAL-3048 not needed for Jalview-JS - save colours
963 JalviewFileChooser chooser = new JalviewFileChooser("fc",
964 SEQUENCE_FEATURE_COLOURS);
965 chooser.setFileView(new JalviewFileView());
966 chooser.setDialogTitle(
967 MessageManager.getString("label.save_feature_colours"));
968 chooser.setToolTipText(MessageManager.getString("action.save"));
970 int value = chooser.showSaveDialog(this);
972 if (value == JalviewFileChooser.APPROVE_OPTION)
974 save(chooser.getSelectedFile());
979 * Saves feature colours and filters to the given file
985 JalviewUserColours ucs = new JalviewUserColours();
986 ucs.setSchemeName("Sequence Features");
989 PrintWriter out = new PrintWriter(new OutputStreamWriter(
990 new FileOutputStream(file), "UTF-8"));
993 * sort feature types by colour order, from 0 (highest)
996 Set<String> fr_colours = fr.getAllFeatureColours();
997 String[] sortedTypes = fr_colours
998 .toArray(new String[fr_colours.size()]);
999 Arrays.sort(sortedTypes, new Comparator<String>()
1002 public int compare(String type1, String type2)
1004 return Float.compare(fr.getOrder(type1), fr.getOrder(type2));
1009 * save feature colours
1011 for (String featureType : sortedTypes)
1013 FeatureColourI fcol = fr.getFeatureStyle(featureType);
1014 jalview.schemabinding.version2.Colour col = Jalview2XML.marshalColour(
1020 * save any feature filters
1022 for (String featureType : sortedTypes)
1024 FeatureMatcherSetI filter = fr.getFeatureFilter(featureType);
1025 if (filter != null && !filter.isEmpty())
1027 Iterator<FeatureMatcherI> iterator = filter.getMatchers().iterator();
1028 FeatureMatcherI firstMatcher = iterator.next();
1029 MatcherSet ms = Jalview2XML.marshalFilter(firstMatcher, iterator,
1031 Filter filterModel = new Filter();
1032 filterModel.setFeatureType(featureType);
1033 filterModel.setMatcherSet(ms);
1034 ucs.addFilter(filterModel);
1040 } catch (Exception ex)
1042 ex.printStackTrace();
1046 public void invertSelection()
1048 Object[][] data = ((FeatureTableModel) table.getModel()).getData();
1049 for (int i = 0; i < data.length; i++)
1051 data[i][SHOW_COLUMN] = !(Boolean) data[i][SHOW_COLUMN];
1053 updateFeatureRenderer(data, true);
1057 public void orderByAvWidth()
1059 if (table == null || table.getModel() == null)
1063 Object[][] data = ((FeatureTableModel) table.getModel()).getData();
1064 float[] width = new float[data.length];
1068 for (int i = 0; i < data.length; i++)
1070 awidth = typeWidth.get(data[i][TYPE_COLUMN]);
1073 width[i] = awidth[1] / awidth[0];// *awidth[0]*awidth[2]; - better
1074 // weight - but have to make per
1075 // sequence, too (awidth[2])
1076 // if (width[i]==1) // hack to distinguish single width sequences.
1087 boolean sort = false;
1088 for (int i = 0; i < width.length; i++)
1090 // awidth = (float[]) typeWidth.get(data[i][0]);
1093 width[i] = fr.getOrder(data[i][TYPE_COLUMN].toString());
1096 width[i] = fr.setOrder(data[i][TYPE_COLUMN].toString(),
1102 width[i] /= max; // normalize
1103 fr.setOrder(data[i][TYPE_COLUMN].toString(), width[i]); // store for later
1107 sort = sort || width[i - 1] > width[i];
1112 jalview.util.QuickSort.sort(width, data);
1113 // update global priority order
1116 updateFeatureRenderer(data, false);
1124 frame.setClosed(true);
1125 } catch (Exception exe)
1131 public void updateFeatureRenderer(Object[][] data)
1133 updateFeatureRenderer(data, true);
1137 * Update the priority order of features; only repaint if this changed the order
1138 * of visible features
1143 void updateFeatureRenderer(Object[][] data, boolean visibleNew)
1145 FeatureSettingsBean[] rowData = getTableAsBeans(data);
1147 if (fr.setFeaturePriority(rowData, visibleNew))
1149 af.alignPanel.paintAlignment(true, true);
1154 * Converts table data into an array of data beans
1156 private FeatureSettingsBean[] getTableAsBeans(Object[][] data)
1158 FeatureSettingsBean[] rowData = new FeatureSettingsBean[data.length];
1159 for (int i = 0; i < data.length; i++)
1161 String type = (String) data[i][TYPE_COLUMN];
1162 FeatureColourI colour = (FeatureColourI) data[i][COLOUR_COLUMN];
1163 FeatureMatcherSetI theFilter = (FeatureMatcherSetI) data[i][FILTER_COLUMN];
1164 Boolean isShown = (Boolean) data[i][SHOW_COLUMN];
1165 rowData[i] = new FeatureSettingsBean(type, colour, theFilter,
1171 private void jbInit() throws Exception
1173 this.setLayout(new BorderLayout());
1175 JPanel settingsPane = new JPanel();
1176 settingsPane.setLayout(new BorderLayout());
1178 JPanel bigPanel = new JPanel();
1179 bigPanel.setLayout(new BorderLayout());
1181 groupPanel = new JPanel();
1182 bigPanel.add(groupPanel, BorderLayout.NORTH);
1184 JButton invert = new JButton(
1185 MessageManager.getString("label.invert_selection"));
1186 invert.setFont(JvSwingUtils.getLabelFont());
1187 invert.addActionListener(new ActionListener()
1190 public void actionPerformed(ActionEvent e)
1196 JButton optimizeOrder = new JButton(
1197 MessageManager.getString("label.optimise_order"));
1198 optimizeOrder.setFont(JvSwingUtils.getLabelFont());
1199 optimizeOrder.addActionListener(new ActionListener()
1202 public void actionPerformed(ActionEvent e)
1208 JButton sortByScore = new JButton(
1209 MessageManager.getString("label.seq_sort_by_score"));
1210 sortByScore.setFont(JvSwingUtils.getLabelFont());
1211 sortByScore.addActionListener(new ActionListener()
1214 public void actionPerformed(ActionEvent e)
1216 af.avc.sortAlignmentByFeatureScore(null);
1219 JButton sortByDens = new JButton(
1220 MessageManager.getString("label.sequence_sort_by_density"));
1221 sortByDens.setFont(JvSwingUtils.getLabelFont());
1222 sortByDens.addActionListener(new ActionListener()
1225 public void actionPerformed(ActionEvent e)
1227 af.avc.sortAlignmentByFeatureDensity(null);
1231 JButton help = new JButton(MessageManager.getString("action.help"));
1232 help.setFont(JvSwingUtils.getLabelFont());
1233 help.addActionListener(new ActionListener()
1236 public void actionPerformed(ActionEvent e)
1240 Help.showHelpWindow(HelpId.SequenceFeatureSettings);
1241 } catch (HelpSetException e1)
1243 e1.printStackTrace();
1247 help.setFont(JvSwingUtils.getLabelFont());
1248 help.setText(MessageManager.getString("action.help"));
1249 help.addActionListener(new ActionListener()
1252 public void actionPerformed(ActionEvent e)
1256 Help.showHelpWindow(HelpId.SequenceFeatureSettings);
1257 } catch (HelpSetException e1)
1259 e1.printStackTrace();
1264 JButton cancel = new JButton(MessageManager.getString("action.cancel"));
1265 cancel.setFont(JvSwingUtils.getLabelFont());
1266 cancel.addActionListener(new ActionListener()
1269 public void actionPerformed(ActionEvent e)
1271 fr.setTransparency(originalTransparency);
1272 fr.setFeatureFilters(originalFilters);
1273 updateFeatureRenderer(originalData);
1278 JButton ok = new JButton(MessageManager.getString("action.ok"));
1279 ok.setFont(JvSwingUtils.getLabelFont());
1280 ok.addActionListener(new ActionListener()
1283 public void actionPerformed(ActionEvent e)
1289 JButton loadColours = new JButton(
1290 MessageManager.getString("label.load_colours"));
1291 loadColours.setFont(JvSwingUtils.getLabelFont());
1292 loadColours.setToolTipText(
1293 MessageManager.getString("label.load_colours_tooltip"));
1294 loadColours.addActionListener(new ActionListener()
1297 public void actionPerformed(ActionEvent e)
1303 JButton saveColours = new JButton(
1304 MessageManager.getString("label.save_colours"));
1305 saveColours.setFont(JvSwingUtils.getLabelFont());
1306 saveColours.setToolTipText(
1307 MessageManager.getString("label.save_colours_tooltip"));
1308 saveColours.addActionListener(new ActionListener()
1311 public void actionPerformed(ActionEvent e)
1316 transparency.addChangeListener(new ChangeListener()
1319 public void stateChanged(ChangeEvent evt)
1321 if (!inConstruction)
1323 fr.setTransparency((100 - transparency.getValue()) / 100f);
1324 af.alignPanel.paintAlignment(true, true);
1329 transparency.setMaximum(70);
1330 transparency.setToolTipText(
1331 MessageManager.getString("label.transparency_tip"));
1333 JPanel transPanel = new JPanel(new GridLayout(1, 2));
1334 bigPanel.add(transPanel, BorderLayout.SOUTH);
1336 JPanel transbuttons = new JPanel(new GridLayout(5, 1));
1337 transbuttons.add(optimizeOrder);
1338 transbuttons.add(invert);
1339 transbuttons.add(sortByScore);
1340 transbuttons.add(sortByDens);
1341 transbuttons.add(help);
1342 transPanel.add(transparency);
1343 transPanel.add(transbuttons);
1345 JPanel buttonPanel = new JPanel();
1346 buttonPanel.add(ok);
1347 buttonPanel.add(cancel);
1348 if (!Jalview.isJS())
1351 * no save/load XML in JalviewJS for now
1353 buttonPanel.add(loadColours);
1354 buttonPanel.add(saveColours);
1356 bigPanel.add(scrollPane, BorderLayout.CENTER);
1357 settingsPane.add(bigPanel, BorderLayout.CENTER);
1358 settingsPane.add(buttonPanel, BorderLayout.SOUTH);
1359 this.add(settingsPane);
1363 * Answers a suitable tooltip to show on the colour cell of the table
1367 * if true include 'click to edit' and similar text
1370 public static String getColorTooltip(FeatureColourI fcol,
1377 if (fcol.isSimpleColour())
1379 return withHint ? BASE_TOOLTIP : null;
1381 String description = fcol.getDescription();
1382 description = description.replaceAll("<", "<");
1383 description = description.replaceAll(">", ">");
1384 StringBuilder tt = new StringBuilder(description);
1387 tt.append("<br>").append(BASE_TOOLTIP).append("</br>");
1389 return JvSwingUtils.wrapTooltip(true, tt.toString());
1392 public static void renderGraduatedColor(JLabel comp, FeatureColourI gcol,
1395 boolean thr = false;
1396 StringBuilder tx = new StringBuilder();
1398 if (gcol.isColourByAttribute())
1400 tx.append(FeatureMatcher
1401 .toAttributeDisplayName(gcol.getAttributeName()));
1403 else if (!gcol.isColourByLabel())
1405 tx.append(MessageManager.getString("label.score"));
1408 if (gcol.isAboveThreshold())
1413 if (gcol.isBelowThreshold())
1418 if (gcol.isColourByLabel())
1424 if (!gcol.isColourByAttribute())
1432 Color newColor = gcol.getMaxColour();
1433 comp.setBackground(newColor);
1434 // System.err.println("Width is " + w / 2);
1435 Icon ficon = new FeatureIcon(gcol, comp.getBackground(), w, h, thr);
1436 comp.setIcon(ficon);
1437 // tt+="RGB value: Max (" + newColor.getRed() + ", "
1438 // + newColor.getGreen() + ", " + newColor.getBlue()
1439 // + ")\nMin (" + minCol.getRed() + ", " + minCol.getGreen()
1440 // + ", " + minCol.getBlue() + ")");
1442 comp.setHorizontalAlignment(SwingConstants.CENTER);
1443 comp.setText(tx.toString());
1446 // ///////////////////////////////////////////////////////////////////////
1447 // http://java.sun.com/docs/books/tutorial/uiswing/components/table.html
1448 // ///////////////////////////////////////////////////////////////////////
1449 class FeatureTableModel extends AbstractTableModel
1451 private String[] columnNames = {
1452 MessageManager.getString("label.feature_type"),
1453 MessageManager.getString("action.colour"),
1454 MessageManager.getString("label.filter"),
1455 MessageManager.getString("label.show") };
1457 private Object[][] data;
1459 FeatureTableModel(Object[][] data)
1464 public Object[][] getData()
1469 public void setData(Object[][] data)
1475 public int getColumnCount()
1477 return columnNames.length;
1480 public Object[] getRow(int row)
1486 public int getRowCount()
1492 public String getColumnName(int col)
1494 return columnNames[col];
1498 public Object getValueAt(int row, int col)
1500 return data[row][col];
1504 * Answers the class of the object in column c of the first row of the table
1507 public Class<?> getColumnClass(int c)
1509 Object v = getValueAt(0, c);
1510 return v == null ? null : v.getClass();
1514 public boolean isCellEditable(int row, int col)
1516 return col == 0 ? false : true;
1520 public void setValueAt(Object value, int row, int col)
1522 data[row][col] = value;
1523 fireTableCellUpdated(row, col);
1524 updateFeatureRenderer(data);
1529 class ColorRenderer extends JLabel implements TableCellRenderer
1531 Border unselectedBorder = null;
1533 Border selectedBorder = null;
1535 public ColorRenderer()
1537 setOpaque(true); // MUST do this for background to show up.
1538 setHorizontalTextPosition(SwingConstants.CENTER);
1539 setVerticalTextPosition(SwingConstants.CENTER);
1543 public Component getTableCellRendererComponent(JTable tbl, Object color,
1544 boolean isSelected, boolean hasFocus, int row, int column)
1546 FeatureColourI cellColour = (FeatureColourI) color;
1548 setBackground(tbl.getBackground());
1549 if (!cellColour.isSimpleColour())
1551 Rectangle cr = tbl.getCellRect(row, column, false);
1552 FeatureSettings.renderGraduatedColor(this, cellColour,
1553 (int) cr.getWidth(), (int) cr.getHeight());
1559 setBackground(cellColour.getColour());
1563 if (selectedBorder == null)
1565 selectedBorder = BorderFactory.createMatteBorder(2, 5, 2, 5,
1566 tbl.getSelectionBackground());
1568 setBorder(selectedBorder);
1572 if (unselectedBorder == null)
1574 unselectedBorder = BorderFactory.createMatteBorder(2, 5, 2, 5,
1575 tbl.getBackground());
1577 setBorder(unselectedBorder);
1584 class FilterRenderer extends JLabel implements TableCellRenderer
1586 javax.swing.border.Border unselectedBorder = null;
1588 javax.swing.border.Border selectedBorder = null;
1590 public FilterRenderer()
1592 setOpaque(true); // MUST do this for background to show up.
1593 setHorizontalTextPosition(SwingConstants.CENTER);
1594 setVerticalTextPosition(SwingConstants.CENTER);
1598 public Component getTableCellRendererComponent(JTable tbl,
1599 Object filter, boolean isSelected, boolean hasFocus, int row,
1602 FeatureMatcherSetI theFilter = (FeatureMatcherSetI) filter;
1604 String asText = theFilter.toString();
1605 setBackground(tbl.getBackground());
1606 this.setText(asText);
1611 if (selectedBorder == null)
1613 selectedBorder = BorderFactory.createMatteBorder(2, 5, 2, 5,
1614 tbl.getSelectionBackground());
1616 setBorder(selectedBorder);
1620 if (unselectedBorder == null)
1622 unselectedBorder = BorderFactory.createMatteBorder(2, 5, 2, 5,
1623 tbl.getBackground());
1625 setBorder(unselectedBorder);
1633 * update comp using rendering settings from gcol
1638 public static void renderGraduatedColor(JLabel comp, FeatureColourI gcol)
1640 int w = comp.getWidth(), h = comp.getHeight();
1643 w = (int) comp.getPreferredSize().getWidth();
1644 h = (int) comp.getPreferredSize().getHeight();
1651 renderGraduatedColor(comp, gcol, w, h);
1654 class ColorEditor extends AbstractCellEditor
1655 implements TableCellEditor, ActionListener
1659 FeatureColourI currentColor;
1661 FeatureTypeSettings chooser;
1667 protected static final String EDIT = "edit";
1669 int rowSelected = 0;
1671 public ColorEditor(FeatureSettings fs)
1674 // Set up the editor (from the table's point of view),
1675 // which is a button.
1676 // This button brings up the color chooser dialog,
1677 // which is the editor from the user's point of view.
1678 button = new JButton();
1679 button.setActionCommand(EDIT);
1680 button.addActionListener(this);
1681 button.setBorderPainted(false);
1685 * Handles events from the editor button, and from the colour/filters
1686 * dialog's OK button
1689 public void actionPerformed(ActionEvent e)
1691 if (button == e.getSource())
1693 if (currentColor.isSimpleColour())
1696 * simple colour chooser
1698 String ttl = MessageManager.getString("label.select_colour");
1699 ColourChooserListener listener = new ColourChooserListener() {
1701 public void colourSelected(Color c)
1703 currentColor = new FeatureColour(c);
1704 me.table.setValueAt(currentColor, rowSelected, COLOUR_COLUMN);
1707 JalviewColourChooser.showColourChooser(button, ttl, currentColor.getColour(), listener);
1712 * variable colour and filters dialog
1714 chooser = new FeatureTypeSettings(me.fr, type);
1719 chooser.setRequestFocusEnabled(true);
1720 chooser.requestFocus();
1722 chooser.addActionListener(this);
1723 // Make the renderer reappear.
1724 fireEditingStopped();
1730 * after OK in variable colour dialog, any changes to colour
1731 * (or filters!) are already set in FeatureRenderer, so just
1732 * update table data without triggering updateFeatureRenderer
1734 currentColor = fr.getFeatureColours().get(type);
1735 FeatureMatcherSetI currentFilter = me.fr.getFeatureFilter(type);
1736 if (currentFilter == null)
1738 currentFilter = new FeatureMatcherSet();
1740 Object[] data = ((FeatureTableModel) table.getModel())
1741 .getData()[rowSelected];
1742 data[COLOUR_COLUMN] = currentColor;
1743 data[FILTER_COLUMN] = currentFilter;
1745 fireEditingStopped();
1746 me.table.validate();
1750 // Implement the one CellEditor method that AbstractCellEditor doesn't.
1752 public Object getCellEditorValue()
1754 return currentColor;
1757 // Implement the one method defined by TableCellEditor.
1759 public Component getTableCellEditorComponent(JTable theTable, Object value,
1760 boolean isSelected, int row, int column)
1762 currentColor = (FeatureColourI) value;
1763 this.rowSelected = row;
1764 type = me.table.getValueAt(row, TYPE_COLUMN).toString();
1765 button.setOpaque(true);
1766 button.setBackground(me.getBackground());
1767 if (!currentColor.isSimpleColour())
1769 JLabel btn = new JLabel();
1770 btn.setSize(button.getSize());
1771 FeatureSettings.renderGraduatedColor(btn, currentColor);
1772 button.setBackground(btn.getBackground());
1773 button.setIcon(btn.getIcon());
1774 button.setText(btn.getText());
1779 button.setIcon(null);
1780 button.setBackground(currentColor.getColour());
1787 * The cell editor for the Filter column. It displays the text of any filters
1788 * for the feature type in that row (in full as a tooltip, possible abbreviated
1789 * as display text). On click in the cell, opens the Feature Display Settings
1790 * dialog at the Filters tab.
1792 class FilterEditor extends AbstractCellEditor
1793 implements TableCellEditor, ActionListener
1797 FeatureMatcherSetI currentFilter;
1805 protected static final String EDIT = "edit";
1807 int rowSelected = 0;
1809 public FilterEditor(FeatureSettings me)
1812 button = new JButton();
1813 button.setActionCommand(EDIT);
1814 button.addActionListener(this);
1815 button.setBorderPainted(false);
1819 * Handles events from the editor button
1822 public void actionPerformed(ActionEvent e)
1824 if (button == e.getSource())
1826 FeatureTypeSettings chooser = new FeatureTypeSettings(me.fr, type);
1827 chooser.addActionListener(this);
1828 chooser.setRequestFocusEnabled(true);
1829 chooser.requestFocus();
1830 if (lastLocation != null)
1832 // todo open at its last position on screen
1833 chooser.setBounds(lastLocation.x, lastLocation.y,
1834 chooser.getWidth(), chooser.getHeight());
1837 fireEditingStopped();
1839 else if (e.getSource() instanceof Component)
1843 * after OK in variable colour dialog, any changes to filter
1844 * (or colours!) are already set in FeatureRenderer, so just
1845 * update table data without triggering updateFeatureRenderer
1847 FeatureColourI currentColor = fr.getFeatureColours().get(type);
1848 currentFilter = me.fr.getFeatureFilter(type);
1849 if (currentFilter == null)
1851 currentFilter = new FeatureMatcherSet();
1853 Object[] data = ((FeatureTableModel) table.getModel())
1854 .getData()[rowSelected];
1855 data[COLOUR_COLUMN] = currentColor;
1856 data[FILTER_COLUMN] = currentFilter;
1857 fireEditingStopped();
1858 me.table.validate();
1863 public Object getCellEditorValue()
1865 return currentFilter;
1869 public Component getTableCellEditorComponent(JTable theTable, Object value,
1870 boolean isSelected, int row, int column)
1872 currentFilter = (FeatureMatcherSetI) value;
1873 this.rowSelected = row;
1874 type = me.table.getValueAt(row, TYPE_COLUMN).toString();
1875 button.setOpaque(true);
1876 button.setBackground(me.getBackground());
1877 button.setText(currentFilter.toString());
1878 button.setIcon(null);
1884 class FeatureIcon implements Icon
1886 FeatureColourI gcol;
1890 boolean midspace = false;
1892 int width = 50, height = 20;
1894 int s1, e1; // start and end of midpoint band for thresholded symbol
1896 Color mpcolour = Color.white;
1898 FeatureIcon(FeatureColourI gfc, Color bg, int w, int h, boolean mspace)
1918 public int getIconWidth()
1924 public int getIconHeight()
1930 public void paintIcon(Component c, Graphics g, int x, int y)
1933 if (gcol.isColourByLabel())
1936 g.fillRect(0, 0, width, height);
1937 // need an icon here.
1938 g.setColor(gcol.getMaxColour());
1940 g.setFont(new Font("Verdana", Font.PLAIN, 9));
1942 // g.setFont(g.getFont().deriveFont(
1943 // AffineTransform.getScaleInstance(
1944 // width/g.getFontMetrics().stringWidth("Label"),
1945 // height/g.getFontMetrics().getHeight())));
1947 g.drawString(MessageManager.getString("label.label"), 0, 0);
1952 Color minCol = gcol.getMinColour();
1954 g.fillRect(0, 0, s1, height);
1957 g.setColor(Color.white);
1958 g.fillRect(s1, 0, e1 - s1, height);
1960 g.setColor(gcol.getMaxColour());
1961 g.fillRect(0, e1, width - e1, height);