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.io.JalviewFileChooser;
33 import jalview.io.JalviewFileView;
34 import jalview.schemabinding.version2.Filter;
35 import jalview.schemabinding.version2.JalviewUserColours;
36 import jalview.schemabinding.version2.MatcherSet;
37 import jalview.schemes.FeatureColour;
38 import jalview.util.MessageManager;
39 import jalview.util.Platform;
40 import jalview.viewmodel.seqfeatures.FeatureRendererModel.FeatureSettingsBean;
42 import java.awt.BorderLayout;
43 import java.awt.Color;
44 import java.awt.Component;
45 import java.awt.Dimension;
47 import java.awt.Graphics;
48 import java.awt.GridLayout;
49 import java.awt.Point;
50 import java.awt.Rectangle;
51 import java.awt.event.ActionEvent;
52 import java.awt.event.ActionListener;
53 import java.awt.event.ItemEvent;
54 import java.awt.event.ItemListener;
55 import java.awt.event.MouseAdapter;
56 import java.awt.event.MouseEvent;
57 import java.awt.event.MouseMotionAdapter;
58 import java.beans.PropertyChangeEvent;
59 import java.beans.PropertyChangeListener;
61 import java.io.FileInputStream;
62 import java.io.FileOutputStream;
63 import java.io.InputStreamReader;
64 import java.io.OutputStreamWriter;
65 import java.io.PrintWriter;
66 import java.util.Arrays;
67 import java.util.Comparator;
68 import java.util.HashMap;
69 import java.util.HashSet;
70 import java.util.Hashtable;
71 import java.util.Iterator;
72 import java.util.List;
76 import javax.help.HelpSetException;
77 import javax.swing.AbstractCellEditor;
78 import javax.swing.BorderFactory;
79 import javax.swing.Icon;
80 import javax.swing.JButton;
81 import javax.swing.JCheckBox;
82 import javax.swing.JCheckBoxMenuItem;
83 import javax.swing.JColorChooser;
84 import javax.swing.JDialog;
85 import javax.swing.JInternalFrame;
86 import javax.swing.JLabel;
87 import javax.swing.JLayeredPane;
88 import javax.swing.JMenuItem;
89 import javax.swing.JPanel;
90 import javax.swing.JPopupMenu;
91 import javax.swing.JScrollPane;
92 import javax.swing.JSlider;
93 import javax.swing.JTable;
94 import javax.swing.ListSelectionModel;
95 import javax.swing.SwingConstants;
96 import javax.swing.border.Border;
97 import javax.swing.event.ChangeEvent;
98 import javax.swing.event.ChangeListener;
99 import javax.swing.table.AbstractTableModel;
100 import javax.swing.table.TableCellEditor;
101 import javax.swing.table.TableCellRenderer;
102 import javax.swing.table.TableColumn;
104 public class FeatureSettings extends JPanel
105 implements FeatureSettingsControllerI
107 private static final String SEQUENCE_FEATURE_COLOURS = MessageManager
108 .getString("label.sequence_feature_colours");
111 * column indices of fields in Feature Settings table
113 static final int TYPE_COLUMN = 0;
115 static final int COLOUR_COLUMN = 1;
117 static final int FILTER_COLUMN = 2;
119 static final int SHOW_COLUMN = 3;
121 private static final int COLUMN_COUNT = 4;
123 private static final int MIN_WIDTH = 400;
125 private static final int MIN_HEIGHT = 400;
127 private final static String BASE_TOOLTIP = "Click to edit, right-click for menu";
129 final FeatureRenderer fr;
131 public final AlignFrame af;
134 * 'original' fields hold settings to restore on Cancel
136 Object[][] originalData;
138 private float originalTransparency;
140 private Map<String, FeatureMatcherSetI> originalFilters;
142 final JInternalFrame frame;
144 JScrollPane scrollPane = new JScrollPane();
150 JSlider transparency = new JSlider();
153 * when true, constructor is still executing - so ignore UI events
155 protected volatile boolean inConstruction = true;
157 int selectedRow = -1;
159 JButton fetchDAS = new JButton();
161 JButton saveDAS = new JButton();
163 JButton cancelDAS = new JButton();
165 boolean resettingTable = false;
168 * true when Feature Settings are updating from feature renderer
170 private boolean handlingUpdate = false;
173 * holds {featureCount, totalExtent} for each feature type
175 Map<String, float[]> typeWidth = null;
182 public FeatureSettings(AlignFrame alignFrame)
184 this.af = alignFrame;
185 fr = af.getFeatureRenderer();
187 // save transparency for restore on Cancel
188 originalTransparency = fr.getTransparency();
189 int originalTransparencyAsPercent = (int) (originalTransparency * 100);
190 transparency.setMaximum(100 - originalTransparencyAsPercent);
192 originalFilters = new HashMap<>(fr.getFeatureFilters()); // shallow copy
197 } catch (Exception ex)
199 ex.printStackTrace();
204 static final String tt = "Click to edit, right-click for menu"; // todo i18n
207 public String getToolTipText(MouseEvent e)
210 int column = table.columnAtPoint(e.getPoint());
211 int row = table.rowAtPoint(e.getPoint());
216 tip = JvSwingUtils.wrapTooltip(true, MessageManager
217 .getString("label.feature_settings_click_drag"));
220 FeatureColourI colour = (FeatureColourI) table.getValueAt(row,
222 tip = getColorTooltip(colour);
225 FeatureMatcherSet o = (FeatureMatcherSet) table.getValueAt(row,
228 ? MessageManager.getString("label.filters_tooltip")
237 table.getTableHeader().setFont(new Font("Verdana", Font.PLAIN, 12));
238 table.setFont(new Font("Verdana", Font.PLAIN, 12));
240 // table.setDefaultRenderer(Color.class, new ColorRenderer());
241 // table.setDefaultEditor(Color.class, new ColorEditor(this));
243 table.setDefaultEditor(FeatureColour.class, new ColorEditor(this));
244 table.setDefaultRenderer(FeatureColour.class, new ColorRenderer());
246 table.setDefaultEditor(FeatureMatcherSet.class, new FilterEditor(this));
247 table.setDefaultRenderer(FeatureMatcherSet.class, new FilterRenderer());
249 TableColumn colourColumn = new TableColumn(COLOUR_COLUMN, 75,
250 new ColorRenderer(), new ColorEditor(this));
251 table.addColumn(colourColumn);
253 TableColumn filterColumn = new TableColumn(FILTER_COLUMN, 75,
254 new FilterRenderer(), new FilterEditor(this));
255 table.addColumn(filterColumn);
257 table.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
259 table.addMouseListener(new MouseAdapter()
262 public void mousePressed(MouseEvent evt)
264 selectedRow = table.rowAtPoint(evt.getPoint());
265 String type = (String) table.getValueAt(selectedRow, TYPE_COLUMN);
266 if (evt.isPopupTrigger())
268 Object colour = table.getValueAt(selectedRow, COLOUR_COLUMN);
269 popupSort(selectedRow, type, colour, fr.getMinMax(), evt.getX(),
272 else if (evt.getClickCount() == 2)
274 boolean invertSelection = evt.isAltDown();
275 boolean toggleSelection = Platform.isControlDown(evt);
276 boolean extendSelection = evt.isShiftDown();
277 fr.ap.alignFrame.avc.markColumnsContainingFeatures(
278 invertSelection, extendSelection, toggleSelection, type);
282 // isPopupTrigger fires on mouseReleased on Windows
284 public void mouseReleased(MouseEvent evt)
286 selectedRow = table.rowAtPoint(evt.getPoint());
287 if (evt.isPopupTrigger())
289 String type = (String) table.getValueAt(selectedRow, TYPE_COLUMN);
290 Object colour = table.getValueAt(selectedRow, COLOUR_COLUMN);
291 popupSort(selectedRow, type, colour, fr.getMinMax(), evt.getX(),
297 table.addMouseMotionListener(new MouseMotionAdapter()
300 public void mouseDragged(MouseEvent evt)
302 int newRow = table.rowAtPoint(evt.getPoint());
303 if (newRow != selectedRow && selectedRow != -1 && newRow != -1)
306 * reposition 'selectedRow' to 'newRow' (the dragged to location)
307 * this could be more than one row away for a very fast drag action
308 * so just swap it with adjacent rows until we get it there
310 Object[][] data = ((FeatureTableModel) table.getModel())
312 int direction = newRow < selectedRow ? -1 : 1;
313 for (int i = selectedRow; i != newRow; i += direction)
315 Object[] temp = data[i];
316 data[i] = data[i + direction];
317 data[i + direction] = temp;
319 updateFeatureRenderer(data);
321 selectedRow = newRow;
325 // table.setToolTipText(JvSwingUtils.wrapTooltip(true,
326 // MessageManager.getString("label.feature_settings_click_drag")));
327 scrollPane.setViewportView(table);
329 if (af.getViewport().isShowSequenceFeatures() || !fr.hasRenderOrder())
331 fr.findAllFeatures(true); // display everything!
334 discoverAllFeatureData();
335 final PropertyChangeListener change;
336 final FeatureSettings fs = this;
337 fr.addPropertyChangeListener(change = new PropertyChangeListener()
340 public void propertyChange(PropertyChangeEvent evt)
342 if (!fs.resettingTable && !fs.handlingUpdate)
344 fs.handlingUpdate = true;
346 // new groups may be added with new sequence feature types only
347 fs.handlingUpdate = false;
353 frame = new JInternalFrame();
354 frame.setContentPane(this);
355 if (Platform.isAMac())
357 Desktop.addInternalFrame(frame,
358 MessageManager.getString("label.sequence_feature_settings"),
363 Desktop.addInternalFrame(frame,
364 MessageManager.getString("label.sequence_feature_settings"),
367 frame.setMinimumSize(new Dimension(MIN_WIDTH, MIN_HEIGHT));
369 frame.addInternalFrameListener(
370 new javax.swing.event.InternalFrameAdapter()
373 public void internalFrameClosed(
374 javax.swing.event.InternalFrameEvent evt)
376 fr.removePropertyChangeListener(change);
379 frame.setLayer(JLayeredPane.PALETTE_LAYER);
380 inConstruction = false;
383 protected void popupSort(final int rowSelected, final String type,
384 final Object typeCol, final Map<String, float[][]> minmax, int x,
387 final FeatureColourI featureColour = (FeatureColourI) typeCol;
389 JPopupMenu men = new JPopupMenu(MessageManager
390 .formatMessage("label.settings_for_param", new String[]
392 JMenuItem scr = new JMenuItem(
393 MessageManager.getString("label.sort_by_score"));
395 final FeatureSettings me = this;
396 scr.addActionListener(new ActionListener()
400 public void actionPerformed(ActionEvent e)
403 .sortAlignmentByFeatureScore(Arrays.asList(new String[]
408 JMenuItem dens = new JMenuItem(
409 MessageManager.getString("label.sort_by_density"));
410 dens.addActionListener(new ActionListener()
414 public void actionPerformed(ActionEvent e)
417 .sortAlignmentByFeatureDensity(Arrays.asList(new String[]
425 * variable colour options include colour by label, by score,
426 * by selected attribute text, or attribute value
428 final JCheckBoxMenuItem mxcol = new JCheckBoxMenuItem(
429 MessageManager.getString("label.variable_colour"));
430 mxcol.setSelected(!featureColour.isSimpleColour());
432 mxcol.addActionListener(new ActionListener()
434 JColorChooser colorChooser;
437 public void actionPerformed(ActionEvent e)
439 if (e.getSource() == mxcol)
441 if (featureColour.isSimpleColour())
443 FeatureTypeSettings fc = new FeatureTypeSettings(me.fr, type);
444 fc.addActionListener(this);
448 // bring up simple color chooser
449 colorChooser = new JColorChooser();
450 String title = MessageManager
451 .getString("label.select_colour");
452 JDialog dialog = JColorChooser.createDialog(me,
453 title, true, // modal
454 colorChooser, this, // OK button handler
455 null); // no CANCEL button handler
456 colorChooser.setColor(featureColour.getMaxColour());
457 dialog.setVisible(true);
462 if (e.getSource() instanceof FeatureTypeSettings)
465 * update after OK in feature colour dialog; the updated
466 * colour will have already been set in the FeatureRenderer
468 FeatureColourI fci = fr.getFeatureColours().get(type);
469 table.setValueAt(fci, rowSelected, 1);
474 // probably the color chooser!
475 table.setValueAt(new FeatureColour(colorChooser.getColor()),
478 me.updateFeatureRenderer(
479 ((FeatureTableModel) table.getModel()).getData(),
487 JMenuItem selCols = new JMenuItem(
488 MessageManager.getString("label.select_columns_containing"));
489 selCols.addActionListener(new ActionListener()
492 public void actionPerformed(ActionEvent arg0)
494 fr.ap.alignFrame.avc.markColumnsContainingFeatures(false, false,
498 JMenuItem clearCols = new JMenuItem(MessageManager
499 .getString("label.select_columns_not_containing"));
500 clearCols.addActionListener(new ActionListener()
503 public void actionPerformed(ActionEvent arg0)
505 fr.ap.alignFrame.avc.markColumnsContainingFeatures(true, false,
509 JMenuItem hideCols = new JMenuItem(
510 MessageManager.getString("label.hide_columns_containing"));
511 hideCols.addActionListener(new ActionListener()
514 public void actionPerformed(ActionEvent arg0)
516 fr.ap.alignFrame.hideFeatureColumns(type, true);
519 JMenuItem hideOtherCols = new JMenuItem(
520 MessageManager.getString("label.hide_columns_not_containing"));
521 hideOtherCols.addActionListener(new ActionListener()
524 public void actionPerformed(ActionEvent arg0)
526 fr.ap.alignFrame.hideFeatureColumns(type, false);
532 men.add(hideOtherCols);
533 men.show(table, x, y);
537 synchronized public void discoverAllFeatureData()
539 Set<String> allGroups = new HashSet<>();
540 AlignmentI alignment = af.getViewport().getAlignment();
542 for (int i = 0; i < alignment.getHeight(); i++)
544 SequenceI seq = alignment.getSequenceAt(i);
545 for (String group : seq.getFeatures().getFeatureGroups(true))
547 if (group != null && !allGroups.contains(group))
549 allGroups.add(group);
550 checkGroupState(group);
561 * Synchronise gui group list and check visibility of group
564 * @return true if group is visible
566 private boolean checkGroupState(String group)
568 boolean visible = fr.checkGroupVisibility(group, true);
570 for (int g = 0; g < groupPanel.getComponentCount(); g++)
572 if (((JCheckBox) groupPanel.getComponent(g)).getText().equals(group))
574 ((JCheckBox) groupPanel.getComponent(g)).setSelected(visible);
579 final String grp = group;
580 final JCheckBox check = new JCheckBox(group, visible);
581 check.setFont(new Font("Serif", Font.BOLD, 12));
582 check.setToolTipText(group);
583 check.addItemListener(new ItemListener()
586 public void itemStateChanged(ItemEvent evt)
588 fr.setGroupVisibility(check.getText(), check.isSelected());
589 resetTable(new String[] { grp });
590 af.alignPanel.paintAlignment(true, true);
593 groupPanel.add(check);
597 synchronized void resetTable(String[] groupChanged)
603 resettingTable = true;
604 typeWidth = new Hashtable<>();
605 // TODO: change avWidth calculation to 'per-sequence' average and use long
608 Set<String> displayableTypes = new HashSet<>();
609 Set<String> foundGroups = new HashSet<>();
612 * determine which feature types may be visible depending on
613 * which groups are selected, and recompute average width data
615 for (int i = 0; i < af.getViewport().getAlignment().getHeight(); i++)
618 SequenceI seq = af.getViewport().getAlignment().getSequenceAt(i);
621 * get the sequence's groups for positional features
622 * and keep track of which groups are visible
624 Set<String> groups = seq.getFeatures().getFeatureGroups(true);
625 Set<String> visibleGroups = new HashSet<>();
626 for (String group : groups)
628 if (group == null || checkGroupState(group))
630 visibleGroups.add(group);
633 foundGroups.addAll(groups);
636 * get distinct feature types for visible groups
637 * record distinct visible types, and their count and total length
639 Set<String> types = seq.getFeatures().getFeatureTypesForGroups(true,
640 visibleGroups.toArray(new String[visibleGroups.size()]));
641 for (String type : types)
643 displayableTypes.add(type);
644 float[] avWidth = typeWidth.get(type);
647 avWidth = new float[2];
648 typeWidth.put(type, avWidth);
650 // todo this could include features with a non-visible group
651 // - do we greatly care?
652 // todo should we include non-displayable features here, and only
653 // update when features are added?
654 avWidth[0] += seq.getFeatures().getFeatureCount(true, type);
655 avWidth[1] += seq.getFeatures().getTotalFeatureLength(type);
659 Object[][] data = new Object[displayableTypes.size()][COLUMN_COUNT];
662 if (fr.hasRenderOrder())
666 fr.findAllFeatures(groupChanged != null); // prod to update
667 // colourschemes. but don't
669 // First add the checks in the previous render order,
670 // in case the window has been closed and reopened
672 List<String> frl = fr.getRenderOrder();
673 for (int ro = frl.size() - 1; ro > -1; ro--)
675 String type = frl.get(ro);
677 if (!displayableTypes.contains(type))
682 data[dataIndex][TYPE_COLUMN] = type;
683 data[dataIndex][COLOUR_COLUMN] = fr.getFeatureStyle(type);
684 FeatureMatcherSetI featureFilter = fr.getFeatureFilter(type);
685 data[dataIndex][FILTER_COLUMN] = featureFilter == null
686 ? new FeatureMatcherSet()
688 data[dataIndex][SHOW_COLUMN] = new Boolean(
689 af.getViewport().getFeaturesDisplayed().isVisible(type));
691 displayableTypes.remove(type);
696 * process any extra features belonging only to
697 * a group which was just selected
699 while (!displayableTypes.isEmpty())
701 String type = displayableTypes.iterator().next();
702 data[dataIndex][TYPE_COLUMN] = type;
704 data[dataIndex][COLOUR_COLUMN] = fr.getFeatureStyle(type);
705 if (data[dataIndex][COLOUR_COLUMN] == null)
707 // "Colour has been updated in another view!!"
708 fr.clearRenderOrder();
711 FeatureMatcherSetI featureFilter = fr.getFeatureFilter(type);
712 data[dataIndex][FILTER_COLUMN] = featureFilter == null
713 ? new FeatureMatcherSet()
715 data[dataIndex][SHOW_COLUMN] = new Boolean(true);
717 displayableTypes.remove(type);
720 if (originalData == null)
722 originalData = new Object[data.length][COLUMN_COUNT];
723 for (int i = 0; i < data.length; i++)
725 System.arraycopy(data[i], 0, originalData[i], 0, COLUMN_COUNT);
730 updateOriginalData(data);
733 table.setModel(new FeatureTableModel(data));
734 table.getColumnModel().getColumn(0).setPreferredWidth(200);
736 groupPanel.setLayout(
737 new GridLayout(fr.getFeatureGroupsSize() / 4 + 1, 4));
738 pruneGroups(foundGroups);
739 groupPanel.validate();
741 updateFeatureRenderer(data, groupChanged != null);
742 resettingTable = false;
746 * Updates 'originalData' (used for restore on Cancel) if we detect that changes
747 * have been made outwith this dialog
749 * <li>a new feature type added (and made visible)</li>
750 * <li>a feature colour changed (in the Amend Features dialog)</li>
755 protected void updateOriginalData(Object[][] foundData)
757 // todo LinkedHashMap instead of Object[][] would be nice
759 Object[][] currentData = ((FeatureTableModel) table.getModel())
761 for (Object[] row : foundData)
763 String type = (String) row[TYPE_COLUMN];
764 boolean found = false;
765 for (Object[] current : currentData)
767 if (type.equals(current[TYPE_COLUMN]))
771 * currently dependent on object equality here;
772 * really need an equals method on FeatureColour
774 if (!row[COLOUR_COLUMN].equals(current[COLOUR_COLUMN]))
777 * feature colour has changed externally - update originalData
779 for (Object[] original : originalData)
781 if (type.equals(original[TYPE_COLUMN]))
783 original[COLOUR_COLUMN] = row[COLOUR_COLUMN];
794 * new feature detected - add to original data (on top)
796 Object[][] newData = new Object[originalData.length
798 for (int i = 0; i < originalData.length; i++)
800 System.arraycopy(originalData[i], 0, newData[i + 1], 0,
804 originalData = newData;
810 * Remove from the groups panel any checkboxes for groups that are not in the
811 * foundGroups set. This enables removing a group from the display when the last
812 * feature in that group is deleted.
816 protected void pruneGroups(Set<String> foundGroups)
818 for (int g = 0; g < groupPanel.getComponentCount(); g++)
820 JCheckBox checkbox = (JCheckBox) groupPanel.getComponent(g);
821 if (!foundGroups.contains(checkbox.getText()))
823 groupPanel.remove(checkbox);
829 * reorder data based on the featureRenderers global priority list.
833 private void ensureOrder(Object[][] data)
835 boolean sort = false;
836 float[] order = new float[data.length];
837 for (int i = 0; i < order.length; i++)
839 order[i] = fr.getOrder(data[i][0].toString());
842 order[i] = fr.setOrder(data[i][0].toString(), i / order.length);
846 sort = sort || order[i - 1] > order[i];
851 jalview.util.QuickSort.sort(order, data);
856 * Offers a file chooser dialog, and then loads the feature colours and
857 * filters from file in XML format and unmarshals to Jalview feature settings
861 JalviewFileChooser chooser = new JalviewFileChooser("fc",
862 SEQUENCE_FEATURE_COLOURS);
863 chooser.setFileView(new JalviewFileView());
864 chooser.setDialogTitle(
865 MessageManager.getString("label.load_feature_colours"));
866 chooser.setToolTipText(MessageManager.getString("action.load"));
868 int value = chooser.showOpenDialog(this);
870 if (value == JalviewFileChooser.APPROVE_OPTION)
872 File file = chooser.getSelectedFile();
878 * Loads feature colours and filters from XML stored in the given file
886 InputStreamReader in = new InputStreamReader(
887 new FileInputStream(file), "UTF-8");
889 JalviewUserColours jucs = JalviewUserColours.unmarshal(in);
892 * load feature colours
894 for (int i = jucs.getColourCount() - 1; i >= 0; i--)
896 jalview.schemabinding.version2.Colour newcol = jucs.getColour(i);
897 FeatureColourI colour = Jalview2XML.unmarshalColour(newcol);
898 fr.setColour(newcol.getName(), colour);
899 fr.setOrder(newcol.getName(), i / (float) jucs.getColourCount());
903 * load feature filters; loaded filters will replace any that are
904 * currently defined, other defined filters are left unchanged
906 for (int i = 0; i < jucs.getFilterCount(); i++)
908 jalview.schemabinding.version2.Filter filterModel = jucs
910 String featureType = filterModel.getFeatureType();
911 FeatureMatcherSetI filter = Jalview2XML.unmarshalFilter(featureType,
912 filterModel.getMatcherSet());
913 if (!filter.isEmpty())
915 fr.setFeatureFilter(featureType, filter);
920 * update feature settings table
925 Object[][] data = ((FeatureTableModel) table.getModel())
928 updateFeatureRenderer(data, false);
931 } catch (Exception ex)
933 System.out.println("Error loading User Colour File\n" + ex);
938 * Offers a file chooser dialog, and then saves the current feature colours
939 * and any filters to the selected file in XML format
943 JalviewFileChooser chooser = new JalviewFileChooser("fc",
944 SEQUENCE_FEATURE_COLOURS);
945 chooser.setFileView(new JalviewFileView());
946 chooser.setDialogTitle(
947 MessageManager.getString("label.save_feature_colours"));
948 chooser.setToolTipText(MessageManager.getString("action.save"));
950 int value = chooser.showSaveDialog(this);
952 if (value == JalviewFileChooser.APPROVE_OPTION)
954 save(chooser.getSelectedFile());
959 * Saves feature colours and filters to the given file
965 JalviewUserColours ucs = new JalviewUserColours();
966 ucs.setSchemeName("Sequence Features");
969 PrintWriter out = new PrintWriter(new OutputStreamWriter(
970 new FileOutputStream(file), "UTF-8"));
973 * sort feature types by colour order, from 0 (highest)
976 Set<String> fr_colours = fr.getAllFeatureColours();
977 String[] sortedTypes = fr_colours
978 .toArray(new String[fr_colours.size()]);
979 Arrays.sort(sortedTypes, new Comparator<String>()
982 public int compare(String type1, String type2)
984 return Float.compare(fr.getOrder(type1), fr.getOrder(type2));
989 * save feature colours
991 for (String featureType : sortedTypes)
993 FeatureColourI fcol = fr.getFeatureStyle(featureType);
994 jalview.schemabinding.version2.Colour col = Jalview2XML.marshalColour(
1000 * save any feature filters
1002 for (String featureType : sortedTypes)
1004 FeatureMatcherSetI filter = fr.getFeatureFilter(featureType);
1005 if (filter != null && !filter.isEmpty())
1007 Iterator<FeatureMatcherI> iterator = filter.getMatchers().iterator();
1008 FeatureMatcherI firstMatcher = iterator.next();
1009 MatcherSet ms = Jalview2XML.marshalFilter(firstMatcher, iterator,
1011 Filter filterModel = new Filter();
1012 filterModel.setFeatureType(featureType);
1013 filterModel.setMatcherSet(ms);
1014 ucs.addFilter(filterModel);
1020 } catch (Exception ex)
1022 ex.printStackTrace();
1026 public void invertSelection()
1028 Object[][] data = ((FeatureTableModel) table.getModel()).getData();
1029 for (int i = 0; i < data.length; i++)
1031 data[i][SHOW_COLUMN] = !(Boolean) data[i][SHOW_COLUMN];
1033 updateFeatureRenderer(data, true);
1037 public void orderByAvWidth()
1039 if (table == null || table.getModel() == null)
1043 Object[][] data = ((FeatureTableModel) table.getModel()).getData();
1044 float[] width = new float[data.length];
1048 for (int i = 0; i < data.length; i++)
1050 awidth = typeWidth.get(data[i][TYPE_COLUMN]);
1053 width[i] = awidth[1] / awidth[0];// *awidth[0]*awidth[2]; - better
1054 // weight - but have to make per
1055 // sequence, too (awidth[2])
1056 // if (width[i]==1) // hack to distinguish single width sequences.
1067 boolean sort = false;
1068 for (int i = 0; i < width.length; i++)
1070 // awidth = (float[]) typeWidth.get(data[i][0]);
1073 width[i] = fr.getOrder(data[i][TYPE_COLUMN].toString());
1076 width[i] = fr.setOrder(data[i][TYPE_COLUMN].toString(),
1082 width[i] /= max; // normalize
1083 fr.setOrder(data[i][TYPE_COLUMN].toString(), width[i]); // store for later
1087 sort = sort || width[i - 1] > width[i];
1092 jalview.util.QuickSort.sort(width, data);
1093 // update global priority order
1096 updateFeatureRenderer(data, false);
1104 frame.setClosed(true);
1105 } catch (Exception exe)
1111 public void updateFeatureRenderer(Object[][] data)
1113 updateFeatureRenderer(data, true);
1117 * Update the priority order of features; only repaint if this changed the order
1118 * of visible features
1123 private void updateFeatureRenderer(Object[][] data, boolean visibleNew)
1125 FeatureSettingsBean[] rowData = getTableAsBeans(data);
1127 if (fr.setFeaturePriority(rowData, visibleNew))
1129 af.alignPanel.paintAlignment(true, true);
1134 * Converts table data into an array of data beans
1136 private FeatureSettingsBean[] getTableAsBeans(Object[][] data)
1138 FeatureSettingsBean[] rowData = new FeatureSettingsBean[data.length];
1139 for (int i = 0; i < data.length; i++)
1141 String type = (String) data[i][TYPE_COLUMN];
1142 FeatureColourI colour = (FeatureColourI) data[i][COLOUR_COLUMN];
1143 FeatureMatcherSetI theFilter = (FeatureMatcherSetI) data[i][FILTER_COLUMN];
1144 Boolean isShown = (Boolean) data[i][SHOW_COLUMN];
1145 rowData[i] = new FeatureSettingsBean(type, colour, theFilter,
1151 private void jbInit() throws Exception
1153 this.setLayout(new BorderLayout());
1155 JPanel settingsPane = new JPanel();
1156 settingsPane.setLayout(new BorderLayout());
1158 JPanel bigPanel = new JPanel();
1159 bigPanel.setLayout(new BorderLayout());
1161 groupPanel = new JPanel();
1162 bigPanel.add(groupPanel, BorderLayout.NORTH);
1164 JButton invert = new JButton(
1165 MessageManager.getString("label.invert_selection"));
1166 invert.setFont(JvSwingUtils.getLabelFont());
1167 invert.addActionListener(new ActionListener()
1170 public void actionPerformed(ActionEvent e)
1176 JButton optimizeOrder = new JButton(
1177 MessageManager.getString("label.optimise_order"));
1178 optimizeOrder.setFont(JvSwingUtils.getLabelFont());
1179 optimizeOrder.addActionListener(new ActionListener()
1182 public void actionPerformed(ActionEvent e)
1188 JButton sortByScore = new JButton(
1189 MessageManager.getString("label.seq_sort_by_score"));
1190 sortByScore.setFont(JvSwingUtils.getLabelFont());
1191 sortByScore.addActionListener(new ActionListener()
1194 public void actionPerformed(ActionEvent e)
1196 af.avc.sortAlignmentByFeatureScore(null);
1199 JButton sortByDens = new JButton(
1200 MessageManager.getString("label.sequence_sort_by_density"));
1201 sortByDens.setFont(JvSwingUtils.getLabelFont());
1202 sortByDens.addActionListener(new ActionListener()
1205 public void actionPerformed(ActionEvent e)
1207 af.avc.sortAlignmentByFeatureDensity(null);
1211 JButton help = new JButton(MessageManager.getString("action.help"));
1212 help.setFont(JvSwingUtils.getLabelFont());
1213 help.addActionListener(new ActionListener()
1216 public void actionPerformed(ActionEvent e)
1220 Help.showHelpWindow(HelpId.SequenceFeatureSettings);
1221 } catch (HelpSetException e1)
1223 e1.printStackTrace();
1227 help.setFont(JvSwingUtils.getLabelFont());
1228 help.setText(MessageManager.getString("action.help"));
1229 help.addActionListener(new ActionListener()
1232 public void actionPerformed(ActionEvent e)
1236 Help.showHelpWindow(HelpId.SequenceFeatureSettings);
1237 } catch (HelpSetException e1)
1239 e1.printStackTrace();
1244 JButton cancel = new JButton(MessageManager.getString("action.cancel"));
1245 cancel.setFont(JvSwingUtils.getLabelFont());
1246 cancel.addActionListener(new ActionListener()
1249 public void actionPerformed(ActionEvent e)
1251 fr.setTransparency(originalTransparency);
1252 fr.setFeatureFilters(originalFilters);
1253 updateFeatureRenderer(originalData);
1258 JButton ok = new JButton(MessageManager.getString("action.ok"));
1259 ok.setFont(JvSwingUtils.getLabelFont());
1260 ok.addActionListener(new ActionListener()
1263 public void actionPerformed(ActionEvent e)
1269 JButton loadColours = new JButton(
1270 MessageManager.getString("label.load_colours"));
1271 loadColours.setFont(JvSwingUtils.getLabelFont());
1272 loadColours.setToolTipText(
1273 MessageManager.getString("label.load_colours_tooltip"));
1274 loadColours.addActionListener(new ActionListener()
1277 public void actionPerformed(ActionEvent e)
1283 JButton saveColours = new JButton(
1284 MessageManager.getString("label.save_colours"));
1285 saveColours.setFont(JvSwingUtils.getLabelFont());
1286 saveColours.setToolTipText(
1287 MessageManager.getString("label.save_colours_tooltip"));
1288 saveColours.addActionListener(new ActionListener()
1291 public void actionPerformed(ActionEvent e)
1296 transparency.addChangeListener(new ChangeListener()
1299 public void stateChanged(ChangeEvent evt)
1301 if (!inConstruction)
1303 fr.setTransparency((100 - transparency.getValue()) / 100f);
1304 af.alignPanel.paintAlignment(true, true);
1309 transparency.setMaximum(70);
1310 transparency.setToolTipText(
1311 MessageManager.getString("label.transparency_tip"));
1313 JPanel transPanel = new JPanel(new GridLayout(1, 2));
1314 bigPanel.add(transPanel, BorderLayout.SOUTH);
1316 JPanel transbuttons = new JPanel(new GridLayout(5, 1));
1317 transbuttons.add(optimizeOrder);
1318 transbuttons.add(invert);
1319 transbuttons.add(sortByScore);
1320 transbuttons.add(sortByDens);
1321 transbuttons.add(help);
1322 transPanel.add(transparency);
1323 transPanel.add(transbuttons);
1325 JPanel buttonPanel = new JPanel();
1326 buttonPanel.add(ok);
1327 buttonPanel.add(cancel);
1328 buttonPanel.add(loadColours);
1329 buttonPanel.add(saveColours);
1330 bigPanel.add(scrollPane, BorderLayout.CENTER);
1331 settingsPane.add(bigPanel, BorderLayout.CENTER);
1332 settingsPane.add(buttonPanel, BorderLayout.SOUTH);
1333 this.add(settingsPane);
1337 * Answers a suitable tooltip to show on the colour cell of the table
1342 public String getColorTooltip(FeatureColourI gcol)
1344 if (gcol.isSimpleColour())
1346 return BASE_TOOLTIP;
1348 StringBuilder tt = new StringBuilder();
1349 if (gcol.isAboveThreshold())
1351 tt.append("Thresholded (Above ").append(gcol.getThreshold())
1354 else if (gcol.isBelowThreshold())
1356 tt.append("Thresholded (Below ").append(gcol.getThreshold())
1359 else if (gcol.isColourByLabel())
1361 tt.insert(0, "Coloured by label text ");
1363 tt.append("; ").append(BASE_TOOLTIP);
1364 return tt.toString();
1367 public static void renderGraduatedColor(JLabel comp, FeatureColourI gcol,
1370 boolean thr = false;
1371 StringBuilder tx = new StringBuilder();
1373 if (gcol.isColourByAttribute())
1375 tx.append(FeatureMatcher
1376 .toAttributeDisplayName(gcol.getAttributeName()));
1378 else if (!gcol.isColourByLabel())
1380 tx.append(MessageManager.getString("label.score"));
1383 if (gcol.isAboveThreshold())
1388 if (gcol.isBelowThreshold())
1393 if (gcol.isColourByLabel())
1399 if (!gcol.isColourByAttribute())
1407 Color newColor = gcol.getMaxColour();
1408 comp.setBackground(newColor);
1409 // System.err.println("Width is " + w / 2);
1410 Icon ficon = new FeatureIcon(gcol, comp.getBackground(), w, h, thr);
1411 comp.setIcon(ficon);
1412 // tt+="RGB value: Max (" + newColor.getRed() + ", "
1413 // + newColor.getGreen() + ", " + newColor.getBlue()
1414 // + ")\nMin (" + minCol.getRed() + ", " + minCol.getGreen()
1415 // + ", " + minCol.getBlue() + ")");
1417 comp.setHorizontalAlignment(SwingConstants.CENTER);
1418 comp.setText(tx.toString());
1421 // ///////////////////////////////////////////////////////////////////////
1422 // http://java.sun.com/docs/books/tutorial/uiswing/components/table.html
1423 // ///////////////////////////////////////////////////////////////////////
1424 class FeatureTableModel extends AbstractTableModel
1426 private String[] columnNames = {
1427 MessageManager.getString("label.feature_type"),
1428 MessageManager.getString("action.colour"),
1429 MessageManager.getString("label.filter"),
1430 MessageManager.getString("label.show") };
1432 private Object[][] data;
1434 FeatureTableModel(Object[][] data)
1439 public Object[][] getData()
1444 public void setData(Object[][] data)
1450 public int getColumnCount()
1452 return columnNames.length;
1455 public Object[] getRow(int row)
1461 public int getRowCount()
1467 public String getColumnName(int col)
1469 return columnNames[col];
1473 public Object getValueAt(int row, int col)
1475 return data[row][col];
1479 * Answers the class of the object in column c of the first row of the table
1482 public Class<?> getColumnClass(int c)
1484 Object v = getValueAt(0, c);
1485 return v == null ? null : v.getClass();
1489 public boolean isCellEditable(int row, int col)
1491 return col == 0 ? false : true;
1495 public void setValueAt(Object value, int row, int col)
1497 data[row][col] = value;
1498 fireTableCellUpdated(row, col);
1499 updateFeatureRenderer(data);
1504 class ColorRenderer extends JLabel implements TableCellRenderer
1506 Border unselectedBorder = null;
1508 Border selectedBorder = null;
1510 public ColorRenderer()
1512 setOpaque(true); // MUST do this for background to show up.
1513 setHorizontalTextPosition(SwingConstants.CENTER);
1514 setVerticalTextPosition(SwingConstants.CENTER);
1518 public Component getTableCellRendererComponent(JTable tbl, Object color,
1519 boolean isSelected, boolean hasFocus, int row, int column)
1521 FeatureColourI cellColour = (FeatureColourI) color;
1523 setBackground(tbl.getBackground());
1524 if (!cellColour.isSimpleColour())
1526 Rectangle cr = tbl.getCellRect(row, column, false);
1527 FeatureSettings.renderGraduatedColor(this, cellColour,
1528 (int) cr.getWidth(), (int) cr.getHeight());
1534 setBackground(cellColour.getColour());
1538 if (selectedBorder == null)
1540 selectedBorder = BorderFactory.createMatteBorder(2, 5, 2, 5,
1541 tbl.getSelectionBackground());
1543 setBorder(selectedBorder);
1547 if (unselectedBorder == null)
1549 unselectedBorder = BorderFactory.createMatteBorder(2, 5, 2, 5,
1550 tbl.getBackground());
1552 setBorder(unselectedBorder);
1559 class FilterRenderer extends JLabel implements TableCellRenderer
1561 javax.swing.border.Border unselectedBorder = null;
1563 javax.swing.border.Border selectedBorder = null;
1565 public FilterRenderer()
1567 setOpaque(true); // MUST do this for background to show up.
1568 setHorizontalTextPosition(SwingConstants.CENTER);
1569 setVerticalTextPosition(SwingConstants.CENTER);
1573 public Component getTableCellRendererComponent(JTable tbl,
1574 Object filter, boolean isSelected, boolean hasFocus, int row,
1577 FeatureMatcherSetI theFilter = (FeatureMatcherSetI) filter;
1579 String asText = theFilter.toString();
1580 setBackground(tbl.getBackground());
1581 this.setText(asText);
1586 if (selectedBorder == null)
1588 selectedBorder = BorderFactory.createMatteBorder(2, 5, 2, 5,
1589 tbl.getSelectionBackground());
1591 setBorder(selectedBorder);
1595 if (unselectedBorder == null)
1597 unselectedBorder = BorderFactory.createMatteBorder(2, 5, 2, 5,
1598 tbl.getBackground());
1600 setBorder(unselectedBorder);
1608 * update comp using rendering settings from gcol
1613 public static void renderGraduatedColor(JLabel comp, FeatureColourI gcol)
1615 int w = comp.getWidth(), h = comp.getHeight();
1618 w = (int) comp.getPreferredSize().getWidth();
1619 h = (int) comp.getPreferredSize().getHeight();
1626 renderGraduatedColor(comp, gcol, w, h);
1629 class ColorEditor extends AbstractCellEditor
1630 implements TableCellEditor, ActionListener
1634 FeatureColourI currentColor;
1636 FeatureTypeSettings chooser;
1642 JColorChooser colorChooser;
1646 protected static final String EDIT = "edit";
1648 int rowSelected = 0;
1650 public ColorEditor(FeatureSettings me)
1653 // Set up the editor (from the table's point of view),
1654 // which is a button.
1655 // This button brings up the color chooser dialog,
1656 // which is the editor from the user's point of view.
1657 button = new JButton();
1658 button.setActionCommand(EDIT);
1659 button.addActionListener(this);
1660 button.setBorderPainted(false);
1661 // Set up the dialog that the button brings up.
1662 colorChooser = new JColorChooser();
1663 dialog = JColorChooser.createDialog(button,
1664 MessageManager.getString("label.select_colour"), true, // modal
1665 colorChooser, this, // OK button handler
1666 null); // no CANCEL button handler
1670 * Handles events from the editor button and from the dialog's OK button.
1673 public void actionPerformed(ActionEvent e)
1675 // todo test e.getSource() instead here
1676 if (EDIT.equals(e.getActionCommand()))
1678 // The user has clicked the cell, so
1679 // bring up the dialog.
1680 if (currentColor.isSimpleColour())
1682 // bring up simple color chooser
1683 button.setBackground(currentColor.getColour());
1684 colorChooser.setColor(currentColor.getColour());
1685 dialog.setVisible(true);
1689 // bring up graduated chooser.
1690 chooser = new FeatureTypeSettings(me.fr, type);
1691 chooser.setRequestFocusEnabled(true);
1692 chooser.requestFocus();
1693 chooser.addActionListener(this);
1694 chooser.showTab(true);
1696 // Make the renderer reappear.
1697 fireEditingStopped();
1702 if (currentColor.isSimpleColour())
1705 * read off colour picked in colour chooser after OK pressed
1707 currentColor = new FeatureColour(colorChooser.getColor());
1708 me.table.setValueAt(currentColor, rowSelected, COLOUR_COLUMN);
1713 * after OK in variable colour dialog, any changes to colour
1714 * (or filters!) are already set in FeatureRenderer, so just
1715 * update table data without triggering updateFeatureRenderer
1717 currentColor = fr.getFeatureColours().get(type);
1718 FeatureMatcherSetI currentFilter = me.fr.getFeatureFilter(type);
1719 if (currentFilter == null)
1721 currentFilter = new FeatureMatcherSet();
1723 Object[] data = ((FeatureTableModel) table.getModel())
1724 .getData()[rowSelected];
1725 data[COLOUR_COLUMN] = currentColor;
1726 data[FILTER_COLUMN] = currentFilter;
1728 fireEditingStopped();
1729 me.table.validate();
1733 // Implement the one CellEditor method that AbstractCellEditor doesn't.
1735 public Object getCellEditorValue()
1737 return currentColor;
1740 // Implement the one method defined by TableCellEditor.
1742 public Component getTableCellEditorComponent(JTable theTable, Object value,
1743 boolean isSelected, int row, int column)
1745 currentColor = (FeatureColourI) value;
1746 this.rowSelected = row;
1747 type = me.table.getValueAt(row, TYPE_COLUMN).toString();
1748 button.setOpaque(true);
1749 button.setBackground(me.getBackground());
1750 if (!currentColor.isSimpleColour())
1752 JLabel btn = new JLabel();
1753 btn.setSize(button.getSize());
1754 FeatureSettings.renderGraduatedColor(btn, currentColor);
1755 button.setBackground(btn.getBackground());
1756 button.setIcon(btn.getIcon());
1757 button.setText(btn.getText());
1762 button.setIcon(null);
1763 button.setBackground(currentColor.getColour());
1770 * The cell editor for the Filter column. It displays the text of any filters
1771 * for the feature type in that row (in full as a tooltip, possible abbreviated
1772 * as display text). On click in the cell, opens the Feature Display Settings
1773 * dialog at the Filters tab.
1775 class FilterEditor extends AbstractCellEditor
1776 implements TableCellEditor, ActionListener
1780 FeatureMatcherSetI currentFilter;
1788 protected static final String EDIT = "edit";
1790 int rowSelected = 0;
1792 public FilterEditor(FeatureSettings me)
1795 button = new JButton();
1796 button.setActionCommand(EDIT);
1797 button.addActionListener(this);
1798 button.setBorderPainted(false);
1802 * Handles events from the editor button
1805 public void actionPerformed(ActionEvent e)
1807 if (button == e.getSource())
1809 FeatureTypeSettings chooser = new FeatureTypeSettings(me.fr, type);
1810 chooser.addActionListener(this);
1811 chooser.setRequestFocusEnabled(true);
1812 chooser.requestFocus();
1813 if (lastLocation != null)
1815 // todo open at its last position on screen
1816 chooser.setBounds(lastLocation.x, lastLocation.y,
1817 chooser.getWidth(), chooser.getHeight());
1820 chooser.showTab(false);
1821 fireEditingStopped();
1823 else if (e.getSource() instanceof Component)
1827 * after OK in variable colour dialog, any changes to filter
1828 * (or colours!) are already set in FeatureRenderer, so just
1829 * update table data without triggering updateFeatureRenderer
1831 FeatureColourI currentColor = fr.getFeatureColours().get(type);
1832 currentFilter = me.fr.getFeatureFilter(type);
1833 if (currentFilter == null)
1835 currentFilter = new FeatureMatcherSet();
1837 Object[] data = ((FeatureTableModel) table.getModel())
1838 .getData()[rowSelected];
1839 data[COLOUR_COLUMN] = currentColor;
1840 data[FILTER_COLUMN] = currentFilter;
1841 fireEditingStopped();
1842 me.table.validate();
1847 public Object getCellEditorValue()
1849 return currentFilter;
1853 public Component getTableCellEditorComponent(JTable theTable, Object value,
1854 boolean isSelected, int row, int column)
1856 currentFilter = (FeatureMatcherSetI) value;
1857 this.rowSelected = row;
1858 type = me.table.getValueAt(row, TYPE_COLUMN).toString();
1859 button.setOpaque(true);
1860 button.setBackground(me.getBackground());
1861 button.setText(currentFilter.toString());
1862 button.setToolTipText(currentFilter.toString());
1863 button.setIcon(null);
1869 class FeatureIcon implements Icon
1871 FeatureColourI gcol;
1875 boolean midspace = false;
1877 int width = 50, height = 20;
1879 int s1, e1; // start and end of midpoint band for thresholded symbol
1881 Color mpcolour = Color.white;
1883 FeatureIcon(FeatureColourI gfc, Color bg, int w, int h, boolean mspace)
1903 public int getIconWidth()
1909 public int getIconHeight()
1915 public void paintIcon(Component c, Graphics g, int x, int y)
1918 if (gcol.isColourByLabel())
1921 g.fillRect(0, 0, width, height);
1922 // need an icon here.
1923 g.setColor(gcol.getMaxColour());
1925 g.setFont(new Font("Verdana", Font.PLAIN, 9));
1927 // g.setFont(g.getFont().deriveFont(
1928 // AffineTransform.getScaleInstance(
1929 // width/g.getFontMetrics().stringWidth("Label"),
1930 // height/g.getFontMetrics().getHeight())));
1932 g.drawString(MessageManager.getString("label.label"), 0, 0);
1937 Color minCol = gcol.getMinColour();
1939 g.fillRect(0, 0, s1, height);
1942 g.setColor(Color.white);
1943 g.fillRect(s1, 0, e1 - s1, height);
1945 g.setColor(gcol.getMaxColour());
1946 g.fillRect(0, e1, width - e1, height);